Skip to content

Commit 28ab719

Browse files
committed
Initial commit
0 parents  commit 28ab719

18 files changed

+1154
-0
lines changed

.babelrc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"presets": [
3+
"es2015",
4+
"react"
5+
],
6+
"plugins": [
7+
"syntax-object-rest-spread",
8+
"transform-object-rest-spread"
9+
]
10+
}

.eslintignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
packages/*/node_modules
2+
node_modules
3+
gh-pages
4+
dist
5+
build
6+
coverage
7+

.eslintrc.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module.exports = {
2+
"extends": "airbnb",
3+
"parser": "babel-eslint",
4+
"env": {
5+
"browser": true,
6+
"jasmine": true,
7+
"node": true
8+
},
9+
"plugins": [
10+
"react"
11+
],
12+
"rules": {
13+
"comma-dangle": ["error", "never"], // personal preference
14+
"prefer-arrow-callback": 0, // mocha tests (recommendation)
15+
"func-names": 0, // mocha tests (recommendation)
16+
"import/extensions": 0, // skip import extensions
17+
"import/no-extraneous-dependencies": 0, // monorepo setup
18+
"import/no-unresolved": [1, { ignore: ['^reactabular'] }], // monorepo setup
19+
"no-underscore-dangle": 0, // implementation detail (_highlights etc.)
20+
"no-unused-expressions": 0, // chai
21+
"no-use-before-define": 0, // personal preference
22+
"react/forbid-prop-types": 0, // TODO: re-enable this later
23+
"react/sort-comp": 0, // personal preference
24+
"react/no-multi-comp": 0, // personal preference
25+
"react/jsx-filename-extension": 0, // personal preference
26+
"jsx-a11y/no-static-element-interactions": 0 // personal preference
27+
}
28+
};

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules/
2+
coverage/
3+
dist/
4+
.eslintcache
5+

.travis.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
language: node_js
2+
node_js:
3+
- "4"
4+
- "5"
5+
- "6"
6+
script:
7+
- npm run test
8+
after_success:
9+
- bash <(curl -s https://codecov.io/bash)

CHANGELOG.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
6.1.0 / 2016-11-25
2+
==================
3+
4+
* The repository was moved to a new place. Functionality hasn't changed even though the version has been bumped for technical reasons.
5+
6+
4.1.0 / 2016-09-20
7+
==================
8+
9+
* Feature - Pass `extraParameters` to `edit` interface. #201
10+
11+
2.0.2 / 2016-08-17
12+
==================
13+
14+
* Feature - Allow value rendering to be customized. Now you can pass a custom renderer for value if the default (no visible value!) isn't enough.
15+
16+
2.0.0 / 2016-08-16
17+
==================
18+
19+
* Breaking - Rename as `react-edit` given it's a generic component. It's not distributed as a part of `reactabular` anymore and you'll have to install it separately. #176.
20+
* Breaking - Auto focus `input` by default. #180.
21+
22+
1.2.1 / 2016-08-05
23+
==================
24+
25+
* Feature - Expose `event` to `onActivate`.
26+
27+
1.1.6 / 2016-08-05
28+
==================
29+
30+
* Feature - Allow `activateEvent` (default `onClick`) to be overridden.
31+
32+
1.1.0 / 2016-08-03
33+
==================
34+
35+
* Feature - Allow `editingProps` (`value`/`onValue`) to be overridden.
36+
37+
1.0.0 / 2016-07-25
38+
==================
39+
40+
* Initial release.

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2016 Juho Vepsäläinen
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# react-edit - Inline editing utilities for React
2+
3+
`react-edit` provides a set of inline editing related utilities for React. The library comes with a couple of basic editors and you can implement your own as long as you follow the same interface (`value`, `onValue` props).
4+
5+
## API
6+
7+
The `edit` transform has been designed to allow inline editing. It expects you to define how to manipulate the state. it also expects you to pass an editor.
8+
9+
**Example:**
10+
11+
```jsx
12+
...
13+
import cloneDeep from 'lodash/cloneDeep';
14+
import findIndex from 'lodash/findIndex';
15+
import * as edit from 'react-edit';
16+
17+
...
18+
19+
// Define how to manipulate rows through edit.
20+
const editable = edit.edit({
21+
// Determine whether the current cell is being edited or not.
22+
isEditing: ({ columnIndex, rowData }) => columnIndex === rowData.editing,
23+
24+
// The user requested activation, mark the current cell as edited.
25+
// IMPORTANT! If you stash the rows at this.state.rows, DON'T
26+
// mutate it as that will break Table.Body optimization check.
27+
//
28+
// You also have access to `event` here.
29+
onActivate: ({ columnIndex, rowData, event }) => {
30+
event.stopPropagation();
31+
32+
const index = findIndex(this.state.rows, { id: rowData.id });
33+
const rows = cloneDeep(this.state.rows);
34+
35+
rows[index].editing = columnIndex;
36+
37+
this.setState({ rows });
38+
},
39+
40+
// Capture the value when the user has finished and update
41+
// application state.
42+
onValue: ({ value, rowData, property }) => {
43+
const index = findIndex(this.state.rows, { id: rowData.id });
44+
const rows = cloneDeep(this.state.rows);
45+
46+
rows[index][property] = value;
47+
rows[index].editing = false;
48+
49+
this.setState({ rows });
50+
},
51+
52+
// It's possible to shape the value passed to the editor. See
53+
// the Excel example for a concrete example.
54+
// getEditedValue: v => v.value
55+
56+
// If you want to change default value/onValue, you can do it through
57+
// editingProps: { value: 'value', onValue: 'onValue' }
58+
59+
// In case you want to trigger activation using something else than
60+
// onClick, adjust it like this:
61+
// activateEvent: 'onDoubleClick'
62+
});
63+
64+
...
65+
66+
// Wrap within an element and render.
67+
React.createElement('div', editable(edit.input())(
68+
value, { columnIndex, rowData }, { ... custom props ... }
69+
), (value, extraParameters, props) => ({
70+
children: <div>{value}</div>
71+
}));
72+
73+
// Or in JSX
74+
<div {...editable(edit.input())(...)} />
75+
```
76+
77+
## Editing Interface
78+
79+
An editor should follow the following interface:
80+
81+
* `({ value, onValue, extraParameters }) => <React element>`
82+
83+
It will receive the current `value` and is expected to emit the result through `onValue` upon completion. You can capture row data, property name, and such through `extraParameters`.
84+
85+
## Editors
86+
87+
`react-edit` provides a few editors by default:
88+
89+
* `edit.boolean({ props: <props> })` - If the initial value is true, allows setting to false and vice versa. Demo value defaults to false always
90+
* `edit.dropdown({ options: [[<value>, <name>]], props: <props> })` - The dropdown expects an array of value-name object pairs and emits the selected one.
91+
* `edit.input({ props: <props> })` - A wrapper for a regular input.
92+
93+
## Writing a Custom Editor
94+
95+
If you want to implement a custom editor, you should accept `value` and `onValue` prop pair. The former will contain the current value and `onValue` should return a new one. It can be convenient to curry your editor so that you can pass custom `props` to it easily. Consider the following example.
96+
97+
```jsx
98+
/*
99+
import React from 'react';
100+
*/
101+
102+
const boolean = ({ props } = {}) => {
103+
const Boolean = ({ value, onValue }) => (
104+
<div {...props}>
105+
<button
106+
disabled={value}
107+
onClick={() => onValue(true)}
108+
>&#10003;
109+
</button>
110+
<button
111+
disabled={!value}
112+
onClick={() => onValue(false)}
113+
>&#10007;
114+
</button>
115+
</div>
116+
);
117+
Boolean.propTypes = {
118+
value: React.PropTypes.any,
119+
onClick: React.PropTypes.func,
120+
onValue: React.PropTypes.func
121+
};
122+
123+
return Boolean;
124+
};
125+
126+
const Boolean = boolean({ style: {
127+
backgroundColor: '#ddd'
128+
}});
129+
130+
<Boolean value onValue={v => alert(`You chose ${v}`)} />
131+
```
132+
133+
## License
134+
135+
MIT. See LICENSE for details.
136+

__tests__/boolean-test.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/* eslint-disable react/prop-types */
2+
import React from 'react';
3+
import TestUtils from 'react-addons-test-utils';
4+
import { boolean } from '../src';
5+
6+
describe('edit.boolean', function () {
7+
it('renders given value', function () {
8+
const testValue = false;
9+
const Boolean = boolean();
10+
const result = TestUtils.renderIntoDocument(
11+
<Wrapper>
12+
<Boolean value={testValue} onValue={() => {}} />
13+
</Wrapper>
14+
);
15+
16+
const renderedButtons = TestUtils.scryRenderedDOMComponentsWithTag(
17+
result, 'button'
18+
);
19+
20+
expect(renderedButtons.length).toEqual(2);
21+
expect(renderedButtons[0].disabled).toEqual(testValue);
22+
expect(renderedButtons[1].disabled).toEqual(!testValue);
23+
});
24+
25+
it('triggers onValue when value is false', function () {
26+
let changedValue = false;
27+
const Boolean = boolean();
28+
const result = TestUtils.renderIntoDocument(
29+
<Wrapper>
30+
<Boolean
31+
value={false}
32+
onValue={() => {
33+
changedValue = true;
34+
}}
35+
/>
36+
</Wrapper>
37+
);
38+
39+
const renderedButtons = TestUtils.scryRenderedDOMComponentsWithTag(
40+
result, 'button'
41+
);
42+
43+
TestUtils.Simulate.click(renderedButtons[0]);
44+
45+
expect(changedValue).toEqual(true);
46+
});
47+
48+
it('triggers onValue when value is true', function () {
49+
let changedValue = false;
50+
const Boolean = boolean();
51+
const result = TestUtils.renderIntoDocument(
52+
<Wrapper>
53+
<Boolean
54+
value
55+
onValue={() => {
56+
changedValue = false;
57+
}}
58+
/>
59+
</Wrapper>
60+
);
61+
62+
const renderedButtons = TestUtils.scryRenderedDOMComponentsWithTag(
63+
result, 'button'
64+
);
65+
66+
TestUtils.Simulate.click(renderedButtons[1]);
67+
68+
expect(changedValue).toEqual(false);
69+
});
70+
71+
it('accepts custom props', function () {
72+
const testClassName = 'demo';
73+
const Boolean = boolean({
74+
props: { className: testClassName }
75+
});
76+
const result = TestUtils.renderIntoDocument(
77+
<Wrapper>
78+
<Boolean value={'name'} onValue={() => {}} />
79+
</Wrapper>
80+
);
81+
82+
const renderedDiv = TestUtils.findRenderedDOMComponentWithClass(
83+
result, testClassName
84+
);
85+
86+
expect(renderedDiv).toBeDefined();
87+
});
88+
});
89+
90+
class Wrapper extends React.Component { // eslint-disable-line max-len, react/prefer-stateless-function
91+
render() {
92+
return <div>{this.props.children}</div>;
93+
}
94+
}

0 commit comments

Comments
 (0)