Add SPA for GitHub Pages boilerplate

This commit is contained in:
Daniel
2017-04-27 15:03:28 -04:00
commit e87edf45a1
24 changed files with 822 additions and 0 deletions

70
src/components/App.js Normal file
View File

@@ -0,0 +1,70 @@
import React, { PropTypes } from 'react';
import Interactive from 'react-interactive';
import { Link } from 'react-router';
import s from '../styles/app.style';
const propTypes = {
children: PropTypes.element.isRequired,
routes: PropTypes.array.isRequired,
};
function App({ children, routes }) {
function generateMapMenu() {
let path = '';
function nextPath(route) {
path += (
(path.slice(-1) === '/' ? '' : '/') +
(route.path === '/' ? '' : route.path)
);
return path;
}
return (
routes.filter(route => route.mapMenuTitle)
.map((route, index, array) => (
<span key={index}>
<Interactive
as={Link}
{...s.link}
to={nextPath(route)}
>{route.mapMenuTitle}</Interactive>
{(index + 1) < array.length && ' / '}
</span>
))
);
}
return (
<div style={s.root}>
<h1 style={s.title}>Single Page Apps for GitHub Pages</h1>
<Interactive
as="a"
href="https://github.com/rafrex/spa-github-pages"
style={s.repoLink}
{...s.link}
>https://github.com/rafrex/spa-github-pages</Interactive>
<nav style={s.mapMenu}>
{generateMapMenu()}
</nav>
{children}
<div style={s.creditLine}>
<Interactive
as="a"
href="http://www.rafaelpedicini.com"
interactiveChild
focus={{}}
touchActive={{}}
touchActiveTapOnly
>
Code and concept by <span {...s.childLink}>Rafael Pedicini</span>
</Interactive>
</div>
</div>
);
}
App.propTypes = propTypes;
export default App;

View File

@@ -0,0 +1,33 @@
import React, { PropTypes } from 'react';
import Interactive from 'react-interactive';
import { Link } from 'react-router';
import s from '../styles/exampleComponent.style';
const propTypes = {
children: PropTypes.element,
};
function ExampleComponent({ children }) {
return (
<div>
<p style={s.p}>
This is an example page. Refresh the page or copy/paste the url to
test out the redirect functionality (this same page should load
after the redirect).
</p>
{children ||
<div style={s.pageLinkContainer}>
<Interactive
as={Link}
{...s.link}
to="/example/two-deep?field1=foo&field2=bar#boom!"
>Example two deep with query and hash</Interactive>
</div>
}
</div>
);
}
ExampleComponent.propTypes = propTypes;
export default ExampleComponent;

View File

@@ -0,0 +1,71 @@
import React, { PropTypes } from 'react';
import Interactive from 'react-interactive';
import { Link } from 'react-router';
import s from '../styles/exampleTwoDeepComponent.style';
const propTypes = {
location: PropTypes.object.isRequired,
};
function ExampleTwoDeepComponent({ location }) {
const queryPresent = Object.keys(location.query).length !== 0;
const hashPresent = location.hash !== '';
function queryStringTitle() {
if (queryPresent) return 'The query string field-value pairs are:';
return 'No query string in the url';
}
function hashFragmentTitle() {
if (hashPresent) return 'The hash fragment is:';
return 'No hash fragment in the url';
}
function linkToShowQueryAndOrHash() {
if (queryPresent && hashPresent) return null;
const queryString = (queryPresent ? location.search : '?field1=foo&field2=bar');
const hashFragment = (hashPresent ? location.hash : '#boom!');
let linkText = '';
if (queryPresent && !hashPresent) linkText = 'Show with hash fragment';
if (!queryPresent && hashPresent) linkText = 'Show with query string';
if (!queryPresent && !hashPresent) linkText = 'Show with query string and hash fragment';
return (
<div style={s.lineContainer}>
<Interactive
as={Link}
to={`/example/two-deep${queryString}${hashFragment}`}
{...s.link}
>{linkText}</Interactive>
</div>
);
}
return (
<div>
<div style={s.lineContainer}>
<div>{queryStringTitle()}</div>
<ul>
{
Object.keys(location.query).map((field, index) => (
s.li(`${field}: ${location.query[field]}`, { key: index })
))
}
</ul>
</div>
<div style={s.lineContainer}>
<div>{hashFragmentTitle()}</div>
<ul>
{hashPresent ? s.li(location.hash.slice(1)) : undefined}
</ul>
</div>
{linkToShowQueryAndOrHash()}
</div>
);
}
ExampleTwoDeepComponent.propTypes = propTypes;
export default ExampleTwoDeepComponent;

47
src/components/Home.js Normal file
View File

@@ -0,0 +1,47 @@
import React from 'react';
import Interactive from 'react-interactive';
import { Link } from 'react-router';
import s from '../styles/home.style';
function Home() {
const repoReadmeLink = text => (
<Interactive
as="a"
{...s.link}
href="https://github.com/rafrex/spa-github-pages#readme"
>{text}</Interactive>
);
return (
<div>
<p style={s.p}>
This is an example single page app built
with React and React&nbsp;Router using {' '}
{s.code('browserHistory')}. Navigate with the links below and
refresh the page or copy/paste the url to test out the redirect
functionality deployed to overcome GitHub&nbsp;Pages incompatibility
with single page apps (like this one).
</p>
<p style={s.p}>
Please see the {repoReadmeLink('repo readme')} for instructions on how to
use this boilerplate to deploy your own single page app using GitHub Pages.
</p>
<div style={s.pageLinkContainer}>
<Interactive
as={Link}
{...s.link}
to="/example"
>Example page</Interactive>
</div>
<div style={s.pageLinkContainer}>
<Interactive
as={Link}
{...s.link}
to="/example/two-deep?field1=foo&field2=bar#boom!"
>Example two deep with query and hash</Interactive>
</div>
</div>
);
}
export default Home;

View File

@@ -0,0 +1,19 @@
import React, { PropTypes } from 'react';
import s from '../styles/pageNotFound.style';
const propTypes = {
location: PropTypes.object.isRequired,
};
function PageNotFound({ location }) {
return (
<p style={s.p}>
Page not found - the path, {s.code(location.pathname)},
did not match any React Router routes.
</p>
);
}
PageNotFound.propTypes = propTypes;
export default PageNotFound;

31
src/index.js Normal file
View File

@@ -0,0 +1,31 @@
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import App from './components/App';
import Home from './components/Home';
import PageNotFound from './components/PageNotFound';
import ExampleComponent from './components/ExampleComponent';
import ExampleTwoDeepComponent from './components/ExampleTwoDeepComponent';
const routes = (
<Route path="/" mapMenuTitle="Home" component={App}>
<IndexRoute component={Home} />
<Route path="example" mapMenuTitle="Example" component={ExampleComponent}>
<Route path="two-deep" mapMenuTitle="Two Deep" component={ExampleTwoDeepComponent} />
</Route>
<Route path="*" mapMenuTitle="Page Not Found" component={PageNotFound} />
</Route>
);
render(
<Router
history={browserHistory}
routes={routes}
/>,
document.getElementById('root'),
);

53
src/styles/app.style.js Normal file
View File

@@ -0,0 +1,53 @@
import style from './style';
const s = Object.create(style);
s.root = {
fontFamily: 'helvetica neue, helvetica, sans-serif',
fontWeight: '300',
fontSize: '16px',
letterSpacing: '0.025em',
padding: '3vh 0 12vh 0',
width: '500px',
// use responsive max-width to simulate padding/margin to allow
// space for vertical scroll bar without creating horizontal scroll bar
// (if there is padding, the window will scroll horizontally to show the padding)
maxWidth: 'calc(100vw - 40px)',
// center based on vw to prevent content jump when vertical scroll bar show/hide
// note: vw/vh include the width of scroll bars. Note that centering using margin auto
// or % (which doesn't include scroll bars, so changes when scroll bars shown) causes a page jump
position: 'relative',
left: '50vw',
WebkitTransform: 'translate(-50%, 0)',
MozTransform: 'translate(-50%, 0)',
msTransform: 'translate(-50%, 0)',
OTransform: 'translate(-50%, 0)',
transform: 'translate(-50%, 0)',
WebkitTextSizeAdjust: 'none',
MozTextSizeAdjust: 'none',
msTextSizeAdjust: 'none',
textSizeAdjust: 'none',
};
s.title = {
fontSize: '20px',
marginBottom: '0.5vh',
};
s.repoLink = {
fontSize: '14px',
};
s.mapMenu = {
margin: '3vh 0',
};
s.creditLine = {
color: '#A0A0A0',
fontSize: '14px',
marginTop: '50px',
};
export default s;

View File

@@ -0,0 +1,9 @@
import style from './style';
const s = Object.create(style);
s.pageLinkContainer = {
margin: '1vh 0',
};
export default s;

View File

@@ -0,0 +1,9 @@
import style from './style';
const s = Object.create(style);
s.lineContainer = {
margin: '3vh 0',
};
export default s;

9
src/styles/home.style.js Normal file
View File

@@ -0,0 +1,9 @@
import style from './style';
const s = Object.create(style);
s.pageLinkContainer = {
margin: '1vh 0',
};
export default s;

View File

@@ -0,0 +1,5 @@
import style from './style';
const s = Object.create(style);
export default s;

65
src/styles/style.js Normal file
View File

@@ -0,0 +1,65 @@
import React from 'react';
const link = {
normal: {
borderBottom: '1px dotted rgb(0, 168, 0)',
},
hover: {
borderBottom: '1px solid rgb(0, 168, 0)',
color: 'black',
},
active: 'hover',
touchActive: {
borderBottom: '1px dashed rgb(0, 168, 0)',
color: 'black',
},
focusFromTab: {
outline: '2px solid rgb(0, 152, 0)',
outlineOffset: '2px',
color: 'black',
},
touchActiveTapOnly: true,
};
const childLink = {};
Object.keys(link).forEach((key) => {
if (key !== 'touchActiveTapOnly') {
childLink[`onParent${key.slice(0, 1).toUpperCase()}${key.slice(1)}`] = link[key];
}
});
export default {
link,
childLink,
p: {
margin: '3vh 0',
lineHeight: '1.4',
},
// generate text formatted as code
code: content => (
<code
style={{
fontFamily: 'monospace',
fontSize: '15px',
paddingLeft: '2px',
}}
>{content}</code>
),
// custom bullets for li items
li: (content, props) => (
<li
style={{
paddingLeft: '18px',
textIndent: '-15px',
margin: '0.5vh 0',
listStyle: 'none',
}}
{...props}
>
<span style={{ paddingRight: '7px' }}>&ndash;</span>
{content}
</li>
),
};