diff --git a/src/table/src/EditableCell.js b/src/table/src/EditableCell.js
index e9ab3420d..bdc37e83d 100644
--- a/src/table/src/EditableCell.js
+++ b/src/table/src/EditableCell.js
@@ -2,6 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import { withTheme } from '../../theme'
import { Portal } from '../../portal'
+import { Stack } from '../../stack'
import TextTableCell from './TextTableCell'
import TableCell from './TableCell'
import EditableCellField from './EditableCellField'
@@ -13,6 +14,22 @@ class EditableCell extends React.PureComponent {
*/
...TableCell.propTypes,
+ /*
+ * Makes the TableCell focusable.
+ * Will add tabIndex={-1 || this.props.tabIndex}.
+ */
+ isSelectable: PropTypes.bool,
+
+ /**
+ * When true, the cell can't be edited.
+ */
+ disabled: PropTypes.bool,
+
+ /**
+ * Optional placeholder when children is falsy.
+ */
+ placeholder: PropTypes.node,
+
/**
* The size used for the TextTableCell and Textarea.
*/
@@ -21,7 +38,7 @@ class EditableCell extends React.PureComponent {
/**
* This is the value of the cell.
*/
- children: PropTypes.string,
+ children: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/**
* Function called when value changes. (value: string) => void.
@@ -30,11 +47,20 @@ class EditableCell extends React.PureComponent {
}
static defaultProps = {
- size: 300
+ size: 300,
+ isSelectable: true
+ }
+
+ static getDerivedStateFromProps(props, state) {
+ if (props.children !== state.value) {
+ return {
+ value: props.children
+ }
+ }
+ return null
}
state = {
- isEditing: false,
value: this.props.children
}
@@ -47,13 +73,21 @@ class EditableCell extends React.PureComponent {
}
handleDoubleClick = () => {
+ if (this.props.disabled || !this.props.isSelectable) return
+
this.setState({
isEditing: true
})
}
handleKeyDown = e => {
+ if (this.props.disabled) return
const { key } = e
+
+ /**
+ * When the user presses a character on the keyboard, use that character
+ * as the value in the text field.
+ */
if (key.match(/^[a-z]{0,10}$/) && !e.metaKey && !e.ctrlKey && !e.altKey) {
this.setState({
isEditing: true,
@@ -67,20 +101,21 @@ class EditableCell extends React.PureComponent {
}
handleFieldBlur = value => {
- const { onChange } = this.props
+ const { onChange, isSelectable } = this.props
const currentValue = this.state.value
this.setState({
isEditing: false,
- // Make edit instantious. Deal with errors up the tree.
value
})
- if (this.mainRef) this.mainRef.focus()
-
if (currentValue !== value && typeof onChange === 'function') {
onChange(value)
}
+
+ if (this.mainRef && isSelectable) {
+ this.mainRef.focus()
+ }
}
handleFieldCancel = () => {
@@ -92,36 +127,50 @@ class EditableCell extends React.PureComponent {
}
render() {
- const { children, theme, size, ...props } = this.props
- const { isEditing } = this.state
+ const {
+ children,
+ theme,
+ size,
+ disabled,
+ placeholder,
+ isSelectable,
+ ...props
+ } = this.props
+ const { isEditing, value } = this.state
return (
- {children}
+ {children ? children : placeholder}
{isEditing && (
- this.mainRef}
- value={children}
- onEscape={this.handleFieldEscape}
- onBlur={this.handleFieldBlur}
- onCancel={this.handleFieldCancel}
- size={size}
- />
+
+ {zIndex => (
+ this.mainRef}
+ value={value}
+ onEscape={this.handleFieldEscape}
+ onBlur={this.handleFieldBlur}
+ onCancel={this.handleFieldCancel}
+ size={size}
+ />
+ )}
+
)}
diff --git a/src/table/src/EditableCellField.js b/src/table/src/EditableCellField.js
index 099125846..b75d88e92 100644
--- a/src/table/src/EditableCellField.js
+++ b/src/table/src/EditableCellField.js
@@ -9,6 +9,11 @@ export default class EditableCellField extends React.PureComponent {
*/
value: PropTypes.string.isRequired,
+ /**
+ * The z-index placed on the element.
+ */
+ zIndex: PropTypes.number.isRequired,
+
/**
* Function to get the target ref of the parent.
* Used to mirror the position.
@@ -71,7 +76,11 @@ export default class EditableCellField extends React.PureComponent {
if (isTableBody) {
return ref
}
- ref = ref.parentElement
+ if (ref.parentElement) {
+ ref = ref.parentElement
+ } else {
+ return null
+ }
}
this.tableBodyRef = ref
@@ -84,15 +93,26 @@ export default class EditableCellField extends React.PureComponent {
if (!targetRef) return
const tableBodyRef = this.getTableBodyRef(targetRef)
- const bounds = tableBodyRef.getBoundingClientRect()
-
- const { left, top, height, width } = targetRef.getBoundingClientRect()
+ const {
+ left,
+ top: targetTop,
+ height,
+ width
+ } = targetRef.getBoundingClientRect()
+
+ let top
+ if (tableBodyRef) {
+ const bounds = tableBodyRef.getBoundingClientRect()
+ top = Math.min(Math.max(targetTop, bounds.top), bounds.bottom - height)
+ } else {
+ top = targetTop
+ }
this.setState(
() => {
return {
left,
- top: Math.min(Math.max(top, bounds.top), bounds.bottom - height),
+ top,
height,
width
}
@@ -117,11 +137,12 @@ export default class EditableCellField extends React.PureComponent {
const { key } = e
if (key === 'Escape' || key === 'Enter') {
this.textareaRef.blur()
+ e.preventDefault()
}
}
render() {
- const { size, value, minWidth, minHeight } = this.props
+ const { size, value, minWidth, minHeight, zIndex } = this.props
const { left, top, height, width } = this.state
return (
@@ -137,7 +158,8 @@ export default class EditableCellField extends React.PureComponent {
height,
minHeight: Math.max(height, minHeight),
width,
- minWidth: Math.max(width, minWidth)
+ minWidth: Math.max(width, minWidth),
+ zIndex
}}
height={null}
width={null}
diff --git a/src/table/src/SelectMenuCell.js b/src/table/src/SelectMenuCell.js
index c688ffe05..a9dc3ed42 100644
--- a/src/table/src/SelectMenuCell.js
+++ b/src/table/src/SelectMenuCell.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import debounce from 'lodash.debounce'
import { withTheme } from '../../theme'
import { SelectMenu } from '../../select-menu'
+import { Icon } from '../../icon'
import TextTableCell from './TextTableCell'
import TableCell from './TableCell'
@@ -94,6 +95,7 @@ class SelectMenuCell extends React.PureComponent {
}
aria-haspopup
aria-expanded={isShown}
size={size}
diff --git a/src/table/src/TableCell.js b/src/table/src/TableCell.js
index 84fc8ec15..e4892230d 100644
--- a/src/table/src/TableCell.js
+++ b/src/table/src/TableCell.js
@@ -25,6 +25,12 @@ class TableCell extends PureComponent {
*/
appearance: PropTypes.string.isRequired,
+ /**
+ * Optional node to be placed on the right side of the table cell.
+ * Useful for icons and icon buttons.
+ */
+ rightView: PropTypes.node,
+
/**
* Theme provided by ThemeProvider.
*/
@@ -51,6 +57,7 @@ class TableCell extends PureComponent {
flex: 1,
display: 'flex',
alignItems: 'center',
+ flexShrink: 0,
overflow: 'hidden'
}
@@ -101,6 +108,7 @@ class TableCell extends PureComponent {
isSelectable,
tabIndex = -1,
className,
+ rightView,
...props
} = this.props
@@ -122,6 +130,7 @@ class TableCell extends PureComponent {
{...props}
>
{children}
+ {rightView ? rightView : null}
)
}}
diff --git a/src/table/src/TextTableCell.js b/src/table/src/TextTableCell.js
index 3dc35552c..7ca2b4c75 100644
--- a/src/table/src/TextTableCell.js
+++ b/src/table/src/TextTableCell.js
@@ -32,7 +32,7 @@ export default class TextTableCell extends PureComponent {
}
render() {
- const { children, textProps, isNumber, ...props } = this.props
+ const { children, textProps, isNumber, placeholder, ...props } = this.props
return (
diff --git a/src/table/stories/EditableTable.js b/src/table/stories/EditableTable.js
index 33cf19836..34815d521 100644
--- a/src/table/stories/EditableTable.js
+++ b/src/table/stories/EditableTable.js
@@ -1,12 +1,13 @@
import React from 'react'
import faker from 'faker'
import { Table } from '../'
+import { Stack } from '../../stack'
import { Pane } from '../../layers'
const range = N => Array.from({ length: N }, (v, k) => k + 1)
// Generate a bunch of users.
-const users = range(1000).map(index => {
+const users = range(100).map(index => {
const options = range(20).map(i => {
const item = faker.commerce.productName()
return {
@@ -21,6 +22,7 @@ const users = range(1000).map(index => {
name: faker.name.findName(),
email: faker.internet.email(),
options,
+ notes: '',
selected: options[0].value
}
})
@@ -68,49 +70,99 @@ export default class EditableTable extends React.PureComponent {
render() {
return (
-
-
-
-
- Name
-
-
- Email
-
- Product
-
-
- {this.state.users.map(user => {
- return (
-
-
+ {zIndex => {
+ // Stack used for testing only. Not neccesary for functionality.
+ return (
+
+
+
+
- {user.name}
-
-
- {user.email}
-
-
- {user.selected}
-
-
- )
- })}
-
-
-
+ Id
+
+
+ Name
+
+
+ Email
+
+ Product
+ Notes
+
+
+ {this.state.users.map(user => {
+ return (
+
+
+ {user.id}
+
+
+ {user.name}
+
+
+ {user.email}
+
+
+ {user.selected}
+
+
+ {user.notes}
+
+
+ )
+ })}
+
+
+
+ )
+ }}
+
)
}
}
diff --git a/src/table/stories/VirtualTable.js b/src/table/stories/VirtualTable.js
index 847895d8f..03d2c22a1 100644
--- a/src/table/stories/VirtualTable.js
+++ b/src/table/stories/VirtualTable.js
@@ -14,7 +14,8 @@ const randomLengthContent = [
// Generate a bunch of users.
const users = range(1000)
- .map(() => ({
+ .map((user, index) => ({
+ id: index,
name: faker.name.findName(),
email: faker.internet.email(),
height: faker.random.arrayElement([32, 40, 56, 'auto'])
@@ -44,7 +45,7 @@ export default class VirtualTable extends React.PureComponent {
{users.map((user, index) => {
return (
-
+
{index}
{user.height}
{user.name}