Skip to content

Adds an Inline Edit component #336

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 24, 2016
Merged
6 changes: 3 additions & 3 deletions README-dist.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

Welcome to [Design System React](https://design-system-react.herokuapp.com).
Welcome to [Design System React](https://react.lightningdesignsystem.com/).

Presented here are Javascript components based on [Lightning Design System](https://www.lightningdesignsystem.com/) and designed for React.

Expand All @@ -8,6 +8,6 @@ Presented here are Javascript components based on [Lightning Design System](http

## Documentation

[Design System React website](https://design-system-react.herokuapp.com)
[Design System React website](https://react.lightningdesignsystem.com/)

[Salesforce Lightning Design System website](https://www.lightningdesignsystem.com)
[Salesforce Lightning Design System website](https://www.lightningdesignsystem.com/)
26 changes: 4 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ For example, `SLDSMenuDropdown` represents [Lightning Design System Menu > Dropd
and `SLDSLookup` represents [Lightning Design System Lookup > Base](http://www.lightningdesignsystem.com/components/lookups/#base).


This project is in beta and very experimental. Please visit [Design System React](https://react.lightningdesignsystem.com/) for documentation and examples of using the Design System React Components. If you are using the ECMAScript 6 source files in `./components/`, you will need to enable your ES5 transpiler to allow stage-1 proposed features. If you are using Babel, it may be helpful to install the NPM module, `babel-preset-stage-1`, into your project and review the `.babelrc` file in this project.
This project is in beta and very experimental. Please visit [Design System React](https://react.lightningdesignsystem.com/) for documentation and examples of using the Design System React Components. If you are using the ECMAScript 6 source files in `./components/`, you will need to enable your ES5 transpiler to allow stage-1 proposed features. If you are using Babel, it may be helpful to install the NPM module `babel-preset-stage-1` into your project and review the `.babelrc` file in this project.

### Run local server

Expand All @@ -33,7 +33,7 @@ npm run dist

## Getting Started

Note: `design-system-react` is optimized for React 0.14.x and uses Lightning Design System 2.1.0-beta.3.
Note: `design-system-react` is optimized for React 0.14.x and uses Lightning Design System 2.1.0-beta.3. You can find a more complete [getting started guide](https://react.lightningdesignsystem.com/getting-started) on the documentation site.

### NPM

Expand All @@ -42,7 +42,7 @@ Because this project is not open-sourced, we cannot publish it to npm. Therefore
```
# package.json

"design-system-react": "git+ssh://[email protected]:salesforce-ux/design-system-react.git#v0.0.30",
"design-system-react": "git+ssh://[email protected]:salesforce-ux/design-system-react.git#v0.0.32",
```

Then, in your React code, import each Lightning Design System component you need.
Expand Down Expand Up @@ -71,25 +71,7 @@ Note: the SLDSPopoverTooltip requires a focusable element as a child (ie. either
```

## FAQ
1. **What is the [Lightning Design System](http://www.lightningdesignsystem.com/)?**

It is collection of design patterns, components, and guidelines for creating unified UI in the Salesforce ecosystem.

2. **How is the Design System React Library different than the Lightning Design System?**

The Lightning Design System consists of static markup components. The Design System React Library is the ReactJS implementation.

3. **Are the Design System React components accessible?**

We strive to make all components accessible for keyboard users and screen readers. If you find any accessibility bugs, please submit a [Github Issue](https://github.com/salesforce-ux/design-system-react/issues).

4. **Which version of React and Lightning Design System do you support?**

Design System React is optimized for React 0.14.x and uses Lightning Design System 2.1.0-beta.3.

5. **Which browsers are supported?**

We support the same browsers as the Lightning Design System. Please visit [Lightning Design System - FAQ](http://www.lightningdesignsystem.com/faq/#what-browsers-are-supported) for details.
Read our [FAQ](https://react.lightningdesignsystem.com/faq) on the documentation site.

## Contributing to the code base

Expand Down
13 changes: 7 additions & 6 deletions components/forms/input/check-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ let checkProps = function () {};

if (process.env.NODE_ENV !== 'production') {
checkProps = function (COMPONENT, props) {
/* eslint-disable max-len */
oneOfRequiredProperty(COMPONENT, {
assistiveText: props.assistiveText,
label: props.label
});
if (!props.inlineEditTrigger) {
oneOfRequiredProperty(COMPONENT, {
assistiveText: props.assistiveText,
label: props.label
});
}

onlyOneOfProperties(COMPONENT, {
assistiveText: props.assistiveText,
label: props.label
});
/* eslint-enable max-len */
};
}

Expand Down
12 changes: 10 additions & 2 deletions components/forms/input/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ const Input = React.createClass({
* This event fires when the input changes.
*/
onChange: PropTypes.func,
/**
* This event fires when the input is clicked.
*/
onClick: PropTypes.func,
/**
* This event fires when the icon is clicked.
*/
Expand Down Expand Up @@ -174,8 +178,10 @@ const Input = React.createClass({
iconName,
iconPosition,
id,
inlineEditTrigger, // eslint-disable-line react/prop-types
label,
onChange,
onClick,
onIconClick,
placeholder,
readOnly,
Expand Down Expand Up @@ -219,7 +225,7 @@ const Input = React.createClass({
'slds-input-has-icon',
`slds-input-has-icon--${iconPosition}`
], {
'slds-has-divider--bottom': readOnly
'slds-has-divider--bottom': readOnly && !inlineEditTrigger
})}
>
{hasIcon && <InputIcon
Expand All @@ -235,13 +241,15 @@ const Input = React.createClass({
disabled={disabled}
id={id}
onChange={onChange}
onClick={onClick}
placeholder={placeholder}
required={required}
type={type}
value={value}
/>}
{readOnly && <span className="slds-form-element__static">
{readOnly && <span className="slds-form-element__static" onClick={onClick}>
{value}
{inlineEditTrigger}
</span>}
</div>
{errorText && <div className="slds-form-element__help">{errorText}</div>}
Expand Down
218 changes: 218 additions & 0 deletions components/forms/input/inline.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/*
Copyright (c) 2015, salesforce.com, inc. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

// # Inline Edit Component

// Implements an inline edit component based on the [Input design pattern](https://www.lightningdesignsystem.com/components/forms/#input) in React.

// ## Dependencies

// ### React
import React from 'react';
import ReactDOM from 'react-dom';

// ### isFunction
import isFunction from 'lodash.isfunction';

// ## Children
import Button from '../../button';
import Input from './index';

// ### Event Helpers
import { KEYS } from '../../../utilities';

// ## Constants
import { FORMS_INLINE_EDIT } from '../../../utilities/constants';

// Remove the need for `React.PropTypes`
const { PropTypes } = React;

/**
* An inline input is rendered as a label by default. When clicked (or tabbed in), it's rendered as an input. When the focus is lost, the current input value is saved and the input is rendered as a label again.
*/
const InlineEdit = React.createClass({
// ### Display Name
// Always use the canonical component name as the React display name.
displayName: FORMS_INLINE_EDIT,

// ### Prop Types
propTypes: {
/**
* Class names to be added to the outer container of the input.
*/
className: PropTypes.oneOfType([
PropTypes.array,
PropTypes.object,
PropTypes.string
]),
/**
* Disables the Inline Edit component and prevents editing the contents.
*/
disabled: PropTypes.bool,
/**
* Every Inline Edit component must have a unique ID in order to support keyboard navigation and ARIA support.
*/
id: PropTypes.string.isRequired,
/**
* This event fires when the input changes.
*/
onChange: PropTypes.func,
/**
* Typically an Inline Edit component will be of the type text, but like the Input element it includes support for all HTML5 types.
*/
type: PropTypes.oneOf([
'text',
'password',
'datetime',
'datetime-local',
'date',
'month',
'time',
'week',
'number',
'email',
'url',
'search',
'tel',
'color'
]),
/**
* Inline Edit is a controlled component, and will always display this value.
*/
value: PropTypes.string.isRequired
},

getDefaultProps () {
return {
type: 'text'
};
},

getInitialState () {
return {
isEditing: false,
value: null
};
},

// ### Render
render () {
const {
disabled,
value,

// ### Additional properties
// Using [object destructuring](https://facebook.github.io/react/docs/transferring-props.html#transferring-with-...-in-jsx) to pass on any properties which are not explicitly defined.
...props
} = this.props;

const inlineEditTrigger = (
<Button
assistiveText="Edit"
disabled={disabled}
iconName="edit"
iconPosition="right"
variant="icon"
/>
);

if (this.state.isEditing) {
props.iconCategory = 'utility';
props.iconName = 'close';
props.iconPosition = 'right';
props.onIconClick = this.endEditMode;
} else {
props.onClick = this.triggerEditMode;
}

return (
<Input
{...props}
disabled={disabled}
inlineEditTrigger={inlineEditTrigger}
onBlur={this.handleBlur}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
readOnly={!this.state.isEditing}
value={this.state.isEditing ? this.state.value : value}
/>
);
},

componentDidUpdate () {
if (this.autoFocus) {
const input = ReactDOM.findDOMNode(this).getElementsByTagName('input')[0];

if (input) {
input.focus();
input.select();
}

this.autoFocus = false;
}
},

triggerEditMode () {
if (!this.props.disabled) {
this.autoFocus = true;
this.setState({
isEditing: true,
value: this.props.value
});
}
},

saveEdits () {
if (isFunction(this.props.onChange)) {
this.props.onChange({
value: this.state.value
});
}

this.endEditMode();
},

endEditMode () {
if (this.willSave) {
clearTimeout(this.willSave);
delete this.willSave;
}

this.setState({
isEditing: false,
value: null
});
},

handleBlur () {
if (!this.willSave) {
this.willSave = setTimeout(this.saveEdits, 100);
}
},

handleChange (event) {
this.setState({
value: event.target.value
});
},

handleKeyDown (event) {
if (event.keyCode) {
if (event.keyCode === KEYS.ESCAPE) {
this.endEditMode();
} else if (event.keyCode === KEYS.ENTER) {
this.saveEdits();
}
}
}
});

module.exports = InlineEdit;
3 changes: 3 additions & 0 deletions components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export Icon from './icon';
export SLDSButtonIcon from './icon/button-icon';
export ButtonIcon from './icon/button-icon';

export SLDSInlineEdit from './forms/input/inline';
export InlineEdit from './forms/input/inline';

export SLDSInput from './forms/input';
export Input from './forms/input';

Expand Down
5 changes: 2 additions & 3 deletions stories/input/index.jsx → stories/forms/input/index.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';

import { FORMS_INPUT } from '../../utilities/constants';
import Input from '../../components/forms/input';
import { FORMS_INPUT } from '../../../utilities/constants';
import Input from '../../../components/forms/input';

const iconClicked = action;

Expand Down Expand Up @@ -50,4 +50,3 @@ storiesOf(FORMS_INPUT, module)
placeholder="Placeholder Text"
/>
));

Loading