Browse Source

2020 Community survey banner (#3289)

* Adding survey banner for community

* fixing up a kludge for the banner, making the graphic look less like swearing

* updating banner start date

* make the banner smaller

* Close as a button

* fixes

* Remove the arrow

* Updating image sizes, making the arrow do a little slide

* Revert "Remove the arrow"

This reverts commit 3dddae8108f27178628a38aefd98fdfb60ae09b0.

* Remove extra space

* Legibility tweaks

* Fixing border radius

* Making close button a real button

* Remove trailing space, plz don't re-add, Prettier

* Centering that banner

* Modified Survey header styles

* Inline Banner content at the usage site

* Fix flash by using CSS variables

Co-authored-by: Dan Abramov <dan.abramov@me.com>
Co-authored-by: Brian Vaughn <bvaughn@fb.com>
main
R Nabors 4 years ago
committed by GitHub
parent
commit
aef8fb3566
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      gatsby-browser.js
  2. 184
      src/components/Banner/Banner.js
  3. 14
      src/components/Banner/BannerContext.js
  4. 106
      src/components/Banner/BannerContextManager.js
  5. 3
      src/components/Banner/index.js
  6. 19
      src/components/LayoutHeader/Header.js
  7. 12
      src/components/MarkdownHeader/MarkdownHeader.js
  8. 11
      src/components/MarkdownPage/MarkdownPage.js
  9. 12
      src/components/StickyResponsiveSidebar/StickyResponsiveSidebar.js
  10. 88
      src/html.js
  11. 1
      src/images/i_close.svg
  12. 10
      src/pages/index.js

5
gatsby-browser.js

@ -8,7 +8,6 @@
const React = require('react');
const ReactDOM = require('react-dom');
const {BannerContextManager} = require('components/Banner');
// Import global styles
require('normalize.css');
@ -23,7 +22,3 @@ window.ReactDOM = ReactDOM;
// A stub function is needed because gatsby won't load this file otherwise
// (https://github.com/gatsbyjs/gatsby/issues/6759)
exports.onClientEntry = () => {};
exports.wrapRootElement = ({element}) => (
<BannerContextManager>{element}</BannerContextManager>
);

184
src/components/Banner/Banner.js

@ -6,28 +6,184 @@
*/
// $FlowFixMe Update Flow
import React, {useContext} from 'react';
import BannerContext from './BannerContext';
import {media} from 'theme';
import React from 'react';
import {colors, fonts, media} from 'theme';
import ExternalLinkSvg from 'templates/components/ExternalLinkSvg';
const linkProps = {
href: 'https://www.surveymonkey.co.uk/r/673TZ7T',
target: '_blank',
rel: 'noopener',
};
export default function Banner() {
const {banner, dismiss} = useContext(BannerContext);
if (banner === null) {
return null;
}
return (
<div
css={{
height: banner.normalHeight,
fontSize: 20,
padding: 20,
textAlign: 'center',
display: 'var(--banner-display)',
height: 'var(--banner-height-normal)',
fontSize: 18,
[media.lessThan('large')]: {
fontSize: 16,
},
[media.lessThan('small')]: {
height: banner.smallHeight,
height: 'var(--banner-height-small)',
fontSize: 14,
},
}}>
{banner.content(dismiss)}
<div
css={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100%',
}}>
<a
css={{
display: 'flex',
marginRight: '1rem',
[media.lessThan('medium')]: {
display: 'none',
},
}}
{...linkProps}>
<svg
xmlns="http://www.w3.org/2000/svg"
css={{
width: 'auto',
height: 35,
}}
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 134 58">
<g>
<path
fill={colors.white}
d="m60.25002,1.61335l32.24974,1.38664l25.24993,5.24999l10.99997,7.99998l3.74999,8.24998l-1.5,8.24998l-6.24998,5.24999l-10.49997,5.24999l-8.99998,2.24999l0.25,2.99999l4.99999,3.74999c0.00018,0.11336 8.50016,3.11335 8.50016,3.11335c0,0 -11.49997,0.5 -11.50015,0.38664c0.00018,0.11336 -13.24979,-1.38664 -13.24996,-1.5c0.00018,0.11336 -4.49981,-2.63663 -4.49999,-2.74999c0.00018,0.11336 -2.74981,-1.63664 -2.74999,-1.75c0.00018,0.11336 -7.9998,1.36336 -7.99998,1.25c0.00018,0.11336 -25.24976,1.61336 -25.24993,1.5c0.00018,0.11336 -21.99976,-1.38664 -21.99994,-1.5c0.00018,0.11336 -17.74978,-3.38663 -17.74995,-3.49999c0.00018,0.11336 -9.7498,-6.38662 -9.74997,-6.49998c0.00018,0.11336 -3.24982,-6.38662 -3.24999,-6.49998c0.00018,0.11336 2.00017,-9.88662 1.99999,-9.99997c0.00018,0.11336 7.75016,-9.38662 7.74998,-9.49997c0.00018,0.11336 19.75012,-9.38662 19.74995,-9.49997c0.00018,0.11336 23.50012,-4.13663 23.49994,-4.24999"
/>
<rect
transform="rotate(-5 37.25019073486327,27.62502670288089)"
height="18"
width="18"
y="18.62503"
x="25.7502"
fill="#ccc"
/>
<rect
transform="rotate(-5 66.00012207031251,28.125024795532198)"
height="18"
width="18"
y="19.37502"
x="56.00012"
fill="#ccc"
/>
<rect
transform="rotate(-5 91.75005340576159,25.875030517578093)"
height="18"
width="18"
y="19"
x="85.00004"
fill="#ccc"
/>
<g transform="translate(0,58) scale(0.10000000149011612,-0.10000000149011612) ">
<path
fill={colors.white}
d="m570,574c-14,-2 -65,-9 -115,-15c-139,-18 -275,-69 -356,-134c-75,-60 -115,-163 -88,-226c41,-99 236,-151 564,-150c122,1 210,6 246,14c51,13 57,12 67,-4c28,-44 237,-67 326,-35l40,14l-45,6c-86,13 -100,18 -130,44c-29,24 -30,27 -13,34c18,8 18,8 0,5c-53,-6 -4,-72 69,-93c49,-14 49,-14 -51,-9c-117,7 -159,16 -189,45c-18,17 -26,18 -56,9c-18,-5 -114,-13 -211,-16c-165,-5 -197,-3 -363,23c-207,34 -284,116 -224,241c57,119 236,203 479,225c197,18 545,-20 671,-74c110,-47 157,-153 104,-234c-14,-22 -97,-73 -150,-92c-16,-6 -23,-11 -15,-11c25,-2 133,54 162,84c59,59 56,147 -9,211c-33,34 -97,68 -146,79c-124,27 -166,35 -257,44c-124,12 -275,19 -310,15z"
/>
<path
fill={colors.text}
d="m377.00009,403.25c-1,-10 -16,-47 -34,-82l-33,-63l-21,36c-24,40 -29,42 -56,21c-21,-16 -18,-22 43,-90l33,-38l19,24c10,13 35,49 56,79c20,30 48,67 62,82c13,15 23,30 20,32c-2,2 -23,7 -46,11c-38,6 -43,4 -43,-12z"
/>
<path
fill={colors.text}
d="m674.7493,403c-1,-10 -16,-47 -34,-82l-33,-63l-21,36c-24,40 -29,42 -56,21c-21,-16 -18,-22 43,-90l33,-38l19,24c10,13 35,49 56,79c20,30 48,67 62,82c13,15 23,30 20,32c-2,2 -23,7 -46,11c-38,6 -43,4 -43,-12z"
/>
<path
fill={colors.text}
d="m965.49854,402.99999c-1,-10 -16,-47 -34,-82l-33,-63l-21,36c-24,40 -29,42 -56,21c-21,-16 -18,-22 43,-90l33,-38l19,24c10,13 35,49 56,79c20,30 48,67 62,82c13,15 23,30 20,32c-2,2 -23,7 -46,11c-38,6 -43,4 -43,-12z"
/>
</g>
</g>
</svg>
</a>
<span
css={{
display: 'flex',
[media.lessThan('small')]: {
flexDirection: 'column',
lineHeight: 1.5,
},
}}>
<span
css={{
marginRight: '0.5rem',
}}>
We want to hear from you!
</span>
<a
css={{
color: '#ddd',
transition: 'color 200ms ease-out',
':hover': {
color: colors.white,
},
}}
{...linkProps}
target="_blank"
rel="noopener">
<span css={{color: colors.brand}}>
Take our 2020 Community Survey!
</span>
<ExternalLinkSvg
cssProps={{
verticalAlign: -2,
display: 'inline-block',
marginLeft: '0.5rem',
color: 'inherit',
}}
/>
</a>
</span>
<div css={{display: 'flex', justifyContent: 'flex-end', flexGrow: 1}}>
<button
css={{
background: 'transparent',
padding: '0.25rem 0.5rem',
borderRadius: '0.25rem',
border: 0,
backgroundColor: 'hsl(222, 14%, 30%)',
color: '#ddd',
cursor: 'pointer',
transition: 'color 200ms ease-out',
':hover': {
color: colors.white,
},
marginLeft: '2rem',
fontSize: fonts.small.fontSize,
}}
onClick={() => {
// See html.js
window.__dismissBanner();
}}>
<svg
xmlns="http://www.w3.org/2000/svg"
css={{
width: 10,
height: 10,
}}
viewBox="0 0 5.8 5.8"
alt="close">
<path
d="M5.8 5.16L3.54 2.9 5.8.65 5.16 0 2.9 2.26.65 0 0 .65 2.26 2.9 0 5.16l.65.64L2.9 3.54 5.16 5.8l.64-.64z"
fill="currentColor"></path>
</svg>
</button>
</div>
</div>
</div>
);
}

14
src/components/Banner/BannerContext.js

@ -1,14 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* @emails react-core
* @flow
*/
import React from 'react';
// $FlowFixMe Update Flow
export default React.createContext({
banner: null,
dismiss() {},
});

106
src/components/Banner/BannerContextManager.js

@ -1,106 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* @emails react-core
* @flow
*/
// $FlowFixMe Update Flow
import React, {useState, useLayoutEffect} from 'react';
import BannerContext from './BannerContext';
let activeBanner = null;
let snoozeStartDate = null;
const today = new Date();
function addTimes(date, days) {
const time = new Date(date);
time.setDate(time.getDate() + days);
return time;
}
// Example usage:
// activeBanner = {
// storageId: 'react_banner_XX',
// normalHeight: 60,
// smallHeight: 80,
// campaignStartDate: '2020-09-20Z', // the Z is for UTC
// campaignEndDate: '2020-10-31Z', // the Z is for UTC
// snoozeForDays: 7,
// content: dismiss => (
// <div>
// <a href="test">Test</a> <button onClick={dismiss}>close</button>
// </div>
// ),
// };
if (activeBanner) {
try {
if (localStorage[activeBanner.storageId]) {
snoozeStartDate = new Date(
parseInt(localStorage.getItem(activeBanner.storageId), 10),
);
}
} catch (err) {
// Ignore.
}
try {
// If it's too early or long past the campaign, don't show the banner:
if (
today < new Date(activeBanner.campaignStartDate) ||
today > new Date(activeBanner.campaignEndDate)
) {
activeBanner = null;
// If we're in the campaign window, but the snooze has been set and it hasn't expired:
} else if (
snoozeStartDate &&
addTimes(snoozeStartDate, activeBanner.snoozeForDays) >= today
) {
activeBanner = null;
}
} catch (err) {
// Ignore.
}
}
type Props = {
children: mixed,
};
export default function BannerContextManager({children}: Props) {
const [bannerContext, setBannerContext] = useState({
banner: null,
dismiss() {},
});
// Apply after hydration.
useLayoutEffect(() => {
if (activeBanner) {
let banner = activeBanner;
setBannerContext({
banner,
dismiss: () => {
try {
localStorage.setItem(banner.storageId, Date.now().toString());
} catch (err) {
// Ignore.
}
// Don't show for next navigations within the session.
activeBanner = null;
// Hide immediately.
setBannerContext({
banner: null,
dismiss() {},
});
},
});
}
}, []);
return (
<BannerContext.Provider value={bannerContext}>
{children}
</BannerContext.Provider>
);
}

3
src/components/Banner/index.js

@ -5,8 +5,5 @@
*/
import Banner from './Banner';
import BannerContext from './BannerContext';
import BannerContextManager from './BannerContextManager';
export default Banner;
export {BannerContext, BannerContextManager};

19
src/components/LayoutHeader/Header.js

@ -20,6 +20,15 @@ import navHeader from '../../../content/headerNav.yml';
import logoSvg from 'icons/logo.svg';
const ContainerWrapper = ({children}) => (
<div
css={{
backgroundColor: 'hsl(222, 14%, 10%)',
}}>
{children}
</div>
);
const Header = ({location}: {location: Location}) => (
<header
css={{
@ -34,6 +43,13 @@ const Header = ({location}: {location: Location}) => (
display: 'none',
},
}}>
<ContainerWrapper>
<Container>
<div style={{position: 'relative'}}>
<Banner />
</div>
</Container>
</ContainerWrapper>
<Container>
<div
css={{
@ -243,9 +259,6 @@ const Header = ({location}: {location: Location}) => (
</div>
</div>
</Container>
<Container>
<Banner />
</Container>
</header>
);

12
src/components/MarkdownHeader/MarkdownHeader.js

@ -7,31 +7,29 @@
import Flex from 'components/Flex';
// $FlowFixMe Update Flow
import React, {useContext} from 'react';
import {BannerContext} from 'components/Banner';
import React from 'react';
import {colors, fonts, media} from 'theme';
const MarkdownHeader = ({title}: {title: string}) => {
const {banner} = useContext(BannerContext);
return (
<Flex type="header" halign="space-between" valign="baseline">
<h1
css={{
color: colors.dark,
marginBottom: 0,
marginTop: 40 + (banner ? banner.normalHeight : 0),
marginTop: 'calc(40px + var(--banner-height-normal))',
...fonts.header,
[media.lessThan('small')]: {
marginTop: 40 + (banner ? banner.smallHeight : 0),
marginTop: 'calc(40px + var(--banner-height-small))',
},
[media.size('medium')]: {
marginTop: 60 + (banner ? banner.normalHeight : 0),
marginTop: 'calc(60px + var(--banner-height-normal))',
},
[media.greaterThan('large')]: {
marginTop: 80 + (banner ? banner.normalHeight : 0),
marginTop: 'calc(80px + var(--banner-height-normal))',
},
}}>
{title}

11
src/components/MarkdownPage/MarkdownPage.js

@ -10,8 +10,7 @@ import Flex from 'components/Flex';
import MarkdownHeader from 'components/MarkdownHeader';
import NavigationFooter from 'templates/components/NavigationFooter';
// $FlowFixMe Update Flow
import React, {useContext} from 'react';
import {BannerContext} from 'components/Banner';
import React from 'react';
import StickyResponsiveSidebar from 'components/StickyResponsiveSidebar';
import TitleAndMetaTags from 'components/TitleAndMetaTags';
import FeedbackForm from 'components/FeedbackForm';
@ -19,7 +18,7 @@ import findSectionForPath from 'utils/findSectionForPath';
import toCommaSeparatedList from 'utils/toCommaSeparatedList';
import {sharedStyles} from 'theme';
import createCanonicalUrl from 'utils/createCanonicalUrl';
import {colors} from 'theme';
import {colors, media} from 'theme';
import type {Node} from 'types';
@ -58,7 +57,6 @@ const MarkdownPage = ({
sectionList,
titlePostfix = '',
}: Props) => {
const {banner} = useContext(BannerContext);
const hasAuthors = authors.length > 0;
const titlePrefix = markdownRemark.frontmatter.title || '';
@ -77,7 +75,10 @@ const MarkdownPage = ({
position: 'relative',
zIndex: 0,
'& h1, & h2, & h3, & h4, & h5, & h6': {
scrollMarginTop: banner ? banner.normalHeight : 0,
scrollMarginTop: 'var(--banner-height-normal)',
[media.lessThan('small')]: {
scrollMarginTop: 'var(--banner-height-small)',
},
},
}}>
<TitleAndMetaTags

12
src/components/StickyResponsiveSidebar/StickyResponsiveSidebar.js

@ -7,7 +7,6 @@
import Container from 'components/Container';
import React, {Component} from 'react';
import {BannerContext} from 'components/Banner';
import Sidebar from 'templates/components/Sidebar';
import {colors, media} from 'theme';
import ChevronSvg from 'templates/components/ChevronSvg';
@ -25,8 +24,6 @@ type Props = {
};
class StickyResponsiveSidebar extends Component<Props, State> {
static contextType = BannerContext;
constructor(props: Props) {
super(props);
@ -45,9 +42,8 @@ class StickyResponsiveSidebar extends Component<Props, State> {
render() {
const {open} = this.state;
const {banner} = this.context;
const smallScreenSidebarStyles = {
top: banner ? banner.smallHeight : 0,
top: 'var(--banner-height-small)',
left: 0,
bottom: 0,
right: 0,
@ -121,18 +117,18 @@ class StickyResponsiveSidebar extends Component<Props, State> {
transition: 'transform 0.5s ease',
}}
css={{
marginTop: 60 + (banner ? banner.normalHeight : 0),
marginTop: 'calc(60px + var(--banner-height-normal))',
[media.size('xsmall')]: {
marginTop: 40,
},
[media.between('small', 'medium')]: {
marginTop: 20 + (banner ? banner.normalHeight : 0),
marginTop: 'calc(20px + var(--banner-height-normal))',
},
[media.between('medium', 'large')]: {
marginTop: 50 + (banner ? banner.normalHeight : 0),
marginTop: 'calc(50px + var(--banner-height-normal))',
},
[media.greaterThan('small')]: {

88
src/html.js

@ -41,6 +41,94 @@ export default class HTML extends React.Component<Props> {
{this.props.headComponents}
</head>
<body {...this.props.bodyAttributes}>
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
/*
BE CAREFUL!
This code is not compiled by our transforms
so it needs to stay compatible with older browsers.
*/
var activeBanner = null;
var snoozeStartDate = null;
var today = new Date();
function addTimes(date, days) {
var time = new Date(date);
time.setDate(time.getDate() + days);
return time;
}
activeBanner = {
storageId: 'reactjs_banner_2020survey',
normalHeight: 50,
smallHeight: 75,
campaignStartDate: '2020-09-27Z', // the Z is for UTC
campaignEndDate: '2020-12-13Z', // the Z is for UTC
snoozeForDays: 7,
};
if (activeBanner) {
try {
if (localStorage[activeBanner.storageId]) {
snoozeStartDate = new Date(
parseInt(localStorage.getItem(activeBanner.storageId), 10),
);
}
} catch (err) {
// Ignore.
}
try {
// If it's too early or long past the campaign, don't show the banner:
if (
today < new Date(activeBanner.campaignStartDate) ||
today > new Date(activeBanner.campaignEndDate)
) {
activeBanner = null;
// If we're in the campaign window, but the snooze has been set and it hasn't expired:
} else if (
snoozeStartDate &&
addTimes(snoozeStartDate, activeBanner.snoozeForDays) >= today
) {
activeBanner = null;
}
} catch (err) {
// Ignore.
}
}
function updateStyles() {
if (activeBanner) {
document.documentElement.style.setProperty('--banner-display', 'block');
document.documentElement.style.setProperty('--banner-height-normal', activeBanner.normalHeight + 'px');
document.documentElement.style.setProperty('--banner-height-small', activeBanner.smallHeight + 'px');
} else {
document.documentElement.style.setProperty('--banner-display', 'none');
document.documentElement.style.setProperty('--banner-height-normal', '0px');
document.documentElement.style.setProperty('--banner-height-small', '0px');
}
}
updateStyles();
window.__dismissBanner = function() {
if (activeBanner) {
try {
localStorage.setItem(activeBanner.storageId, Date.now().toString());
} catch (err) {
// Ignore.
}
// Don't show for next navigations within the session.
activeBanner = null;
updateStyles();
}
};
})();
`,
}}
/>
<div
id="___gatsby"
dangerouslySetInnerHTML={{__html: this.props.body}}

1
src/images/i_close.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5.8 5.8"><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon points="5.8 5.16 3.54 2.9 5.8 0.65 5.16 0 2.9 2.26 0.65 0 0 0.65 2.26 2.9 0 5.16 0.65 5.8 2.9 3.54 5.16 5.8 5.8 5.16"/></g></g></svg>

After

Width:  |  Height:  |  Size: 278 B

10
src/pages/index.js

@ -4,7 +4,6 @@
* @emails react-core
*/
import {BannerContext} from 'components/Banner';
import ButtonLink from 'components/ButtonLink';
import Container from 'components/Container';
import Flex from 'components/Flex';
@ -21,8 +20,6 @@ import {babelURL} from 'site-constants';
import logoWhiteSvg from 'icons/logo-white.svg';
class Home extends Component {
static contextType = BannerContext;
state = {
babelLoaded: false,
};
@ -43,7 +40,6 @@ class Home extends Component {
render() {
const {babelLoaded} = this.state;
const {data, location} = this.props;
const {banner} = this.context;
const {codeExamples, examples, marketing} = data;
const code = codeExamples.edges.reduce((lookup, {node}) => {
@ -60,7 +56,11 @@ class Home extends Component {
<div
css={{
width: '100%',
marginTop: banner ? banner.normalHeight : 0,
marginTop: 'var(--banner-height-normal)',
[media.lessThan('small')]: {
marginTop: 'var(--banner-height-small)',
},
}}>
<header
css={{

Loading…
Cancel
Save