Skip to content

Commit

Permalink
Initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
taion committed Oct 26, 2016
1 parent a408242 commit 69ef749
Show file tree
Hide file tree
Showing 27 changed files with 882 additions and 2 deletions.
14 changes: 14 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"presets": [
["latest", {
"es2015": { "loose": true }
}],
"stage-1",
"react"
],
"plugins": [
"dev-expression",
"transform-runtime", // Need stage-3 Symbol polyfill.
"add-module-exports"
]
}
3 changes: 3 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "4catalyzer-react"
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ jspm_packages

# Optional REPL history
.node_repl_history

# Transpiled code
/lib
22 changes: 22 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
sudo: false

language: node_js
node_js:
- stable

env:
- BROWSER=ChromeCi
- BROWSER=Firefox

cache:
directories:
- node_modules

before_install:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start

branches:
only:
- master
10 changes: 10 additions & 0 deletions examples/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"env": {
"browser": true
},
"rules": {
// Don't fail linting if example dependencies aren't installed.
"import/extensions": "off",
"import/no-unresolved": "off"
}
}
1 change: 1 addition & 0 deletions examples/basic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Found Basic Example
16 changes: 16 additions & 0 deletions examples/basic/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "basic",
"version": "0.1.0",
"private": true,
"devDependencies": {
"react-scripts": "0.7.0"
},
"dependencies": {
"found": "../..",
"react": "^15.3.2",
"react-dom": "^15.3.2"
},
"scripts": {
"start": "react-scripts start"
}
}
14 changes: 14 additions & 0 deletions examples/basic/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Found Basic Example</title>
</head>

<body>
<div id="root"></div>
</body>

</html>
101 changes: 101 additions & 0 deletions examples/basic/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from 'react';
import ReactDOM from 'react-dom';

import createBrowserRouter from 'found/lib/createBrowserRouter';
import Link from 'found/lib/Link';
import RedirectException from 'found/lib/RedirectException';

function LinkItem(props) {
// TODO: Remove the pragma once evcohen/eslint-plugin-jsx-a11y#81 lands.
return (
<li>
<Link // eslint-disable-line jsx-a11y/anchor-has-content
{...props}
activeStyle={{ fontWeight: 'bold' }}
/>
</li>
);
}

const propTypes = {
children: React.PropTypes.node,
};

function App({ children }) {
return (
<div>
<ul>
<LinkItem to="/">
Main
</LinkItem>
<ul>
<LinkItem to="/foo">
Foo
</LinkItem>
<LinkItem to="/bar">
Bar (async)
</LinkItem>
<LinkItem to="/baz">
Baz (redirects to Foo)
</LinkItem>
<LinkItem to="/qux">
Qux (missing)
</LinkItem>
</ul>
</ul>

{children}
</div>
);
}

App.propTypes = propTypes;

const BrowserRouter = createBrowserRouter({
routeConfig: [
{
path: '/',
Component: App,
children: [
{
Component: () => <div>Main</div>,
},
{
path: 'foo',
Component: () => <div>Foo</div>,
},
{
path: 'bar',
getComponent: () => new Promise((resolve) => {
setTimeout(resolve, 1000, ({ data }) => <div>{data}</div>);
}),
getData: () => new Promise((resolve) => {
setTimeout(resolve, 1000, 'Bar');
}),
render: ({ Component, props }) => ( // eslint-disable-line react/prop-types
Component && props ? (
<Component {...props} />
) : (
<div><small>Loading&hellip;</small></div>
)
),
},
{
path: 'baz',
render: () => { throw new RedirectException('/foo'); },
},
],
},
],
});

ReactDOM.render(
<BrowserRouter
renderError={error => (
<div>
{error.status === 404 ? 'Not found' : 'Error'}
</div>
)}
/>,
document.getElementById('root'),
);
39 changes: 37 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
],
"main": "lib/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"build": "rimraf lib && babel src -d lib",
"lint": "eslint examples/*/src src test *.js",
"prepublish": "npm run build",
"test": "npm run lint"
},
"repository": {
"type": "git",
Expand All @@ -24,5 +27,37 @@
"bugs": {
"url": "https://github.com/4Catalyzer/found/issues"
},
"homepage": "https://github.com/4Catalyzer/found#readme"
"homepage": "https://github.com/4Catalyzer/found#readme",
"dependencies": {
"babel-runtime": "^6.18.0",
"farce": "^0.0.2",
"is-promise": "^2.1.0",
"lodash": "^4.16.4",
"path-to-regexp": "^1.6.0",
"react-prop-types": "^0.4.0",
"react-redux": "^4.4.5",
"redux": "^3.6.0",
"warning": "^3.0.0"
},
"peerDependencies": {
"react": ">=0.14.0"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-eslint": "^7.0.0",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-dev-expression": "^0.2.1",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-preset-latest": "^6.16.0",
"babel-preset-react": "^6.16.0",
"babel-preset-stage-1": "^6.16.0",
"eslint": "^3.8.1",
"eslint-config-4catalyzer-react": "^0.1.3",
"eslint-plugin-import": "^1.16.0",
"eslint-plugin-jsx-a11y": "^2.2.3",
"eslint-plugin-react": "^6.4.1",
"react": "^15.3.2",
"react-dom": "^15.3.2",
"rimraf": "^2.5.4"
}
}
3 changes: 3 additions & 0 deletions src/ActionTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
UPDATE_MATCH: '@@found/UPDATE_MATCH',
};
118 changes: 118 additions & 0 deletions src/BaseLink.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React from 'react';
import elementType from 'react-prop-types/lib/elementType';

const propTypes = {
Component: elementType.isRequired,
to: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.object,
]),
match: React.PropTypes.object.isRequired,
activeClassName: React.PropTypes.string,
activeStyle: React.PropTypes.object,
activePropName: React.PropTypes.string,
exact: React.PropTypes.bool.isRequired,
target: React.PropTypes.string,
onClick: React.PropTypes.func,
push: React.PropTypes.func.isRequired,
};

const contextTypes = {
store: React.PropTypes.shape({
farce: React.PropTypes.shape({
createHref: React.PropTypes.func.isRequired,
createLocation: React.PropTypes.func.isRequired,
}).isRequired,
found: React.PropTypes.shape({
isActive: React.PropTypes.func.isRequired,
}).isRequired,
}).isRequired,
};

const defaultProps = {
Component: 'a',
exact: false,
};

class BaseLink extends React.Component {
onClick = (event) => {
const { onClick, target, push, to } = this.props;

if (onClick) {
onClick(event);
}

// Don't do anything if the user's onClick handler prevented default.
// Otherwise, let the browser handle the link with the computed href if the
// event wasn't an unmodified left click, or if the link has a target.
if (
event.defaultPrevented ||
event.metaKey ||
event.altKey ||
event.ctrlKey ||
event.shiftKey ||
event.button !== 0 ||
target
) {
return;
}

event.preventDefault();

// FIXME: When clicking on a link to the same location in the browser, the
// actual becomes a replace rather than a push. We may want the same
// handling – perhaps implemented in the Farce protocol.
push(to);
};

render() {
const {
Component,
to,
match,
activeClassName,
activeStyle,
activePropName,
exact,
...props
} = this.props;

const { farce, found } = this.context.store;

delete props.push; // Used in onClick.

if (activeClassName || activeStyle || activePropName) {
const toLocation = farce.createLocation(to);
const active = found.isActive(toLocation, match, { exact });

if (active) {
if (activeClassName) {
props.className = props.className ?
`${props.className} ${activeClassName}` : activeClassName;
}

if (activeStyle) {
props.style = { ...props.style, ...activeStyle };
}
}

if (activePropName) {
props[activePropName] = active;
}
}

return (
<Component
{...props}
href={farce.createHref(to)}
onClick={this.onClick}
/>
);
}
}

BaseLink.propTypes = propTypes;
BaseLink.contextTypes = contextTypes;
BaseLink.defaultProps = defaultProps;

export default BaseLink;

0 comments on commit 69ef749

Please sign in to comment.