mirror of
https://github.com/chaoticbackup/chaoticbackup.github.io.git
synced 2026-09-07 16:15:26 -05:00
Add SPA for GitHub Pages boilerplate
This commit is contained in:
70
src/components/App.js
Normal file
70
src/components/App.js
Normal 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;
|
||||
33
src/components/ExampleComponent.js
Normal file
33
src/components/ExampleComponent.js
Normal 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;
|
||||
71
src/components/ExampleTwoDeepComponent.js
Normal file
71
src/components/ExampleTwoDeepComponent.js
Normal 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
47
src/components/Home.js
Normal 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 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 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;
|
||||
19
src/components/PageNotFound.js
Normal file
19
src/components/PageNotFound.js
Normal 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
31
src/index.js
Normal 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
53
src/styles/app.style.js
Normal 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;
|
||||
9
src/styles/exampleComponent.style.js
Normal file
9
src/styles/exampleComponent.style.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import style from './style';
|
||||
|
||||
const s = Object.create(style);
|
||||
|
||||
s.pageLinkContainer = {
|
||||
margin: '1vh 0',
|
||||
};
|
||||
|
||||
export default s;
|
||||
9
src/styles/exampleTwoDeepComponent.style.js
Normal file
9
src/styles/exampleTwoDeepComponent.style.js
Normal 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
9
src/styles/home.style.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import style from './style';
|
||||
|
||||
const s = Object.create(style);
|
||||
|
||||
s.pageLinkContainer = {
|
||||
margin: '1vh 0',
|
||||
};
|
||||
|
||||
export default s;
|
||||
5
src/styles/pageNotFound.style.js
Normal file
5
src/styles/pageNotFound.style.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import style from './style';
|
||||
|
||||
const s = Object.create(style);
|
||||
|
||||
export default s;
|
||||
65
src/styles/style.js
Normal file
65
src/styles/style.js
Normal 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' }}>–</span>
|
||||
{content}
|
||||
</li>
|
||||
),
|
||||
};
|
||||
Reference in New Issue
Block a user