diff --git a/javascripts/src/components/common/selectable_table_row/SelectableTableRow.spec.tsx b/javascripts/src/components/common/selectable_table_row/SelectableTableRow.spec.tsx
new file mode 100644
index 00000000..38b6100a
--- /dev/null
+++ b/javascripts/src/components/common/selectable_table_row/SelectableTableRow.spec.tsx
@@ -0,0 +1,46 @@
+// License: LGPL-3.0-or-later
+import * as React from 'react';
+import 'jest';
+import SelectableTableRow from './SelectableTableRow'
+import { ReactWrapper, mount } from 'enzyme';
+import { connectTableRowSelectHandler, TableRowSelectHandlerContext } from './connect';
+
+
+class TestReceivedProviderComponent extends React.Component<{m:string} & TableRowSelectHandlerContext, {}>{
+ render() {
+ return
| ;
+ }
+}
+
+const ReceivedComponent = connectTableRowSelectHandler(TestReceivedProviderComponent)
+
+describe('SelectableTableRow', () => {
+ let providerAndRow: ReactWrapper
+ let onSelect: any;
+ beforeEach(() => {
+ onSelect = jest.fn()
+ providerAndRow = mount()
+ })
+
+ function getTr(): ReactWrapper {
+ return providerAndRow.find('tr')
+ }
+
+ function getProviderComponent() {
+ return providerAndRow.find('TestReceivedProviderComponent').instance() as any
+ }
+
+ it('processes the click properly', () => {
+ getTr().simulate('click')
+
+ expect(onSelect).toBeCalled()
+ })
+
+ it('sends the provider down', () => {
+ const c = getProviderComponent()
+ expect(c.props.selectHandler.onSelect).toBeTruthy()
+ expect(c.props.selectHandler.onSelect).toBe(onSelect)
+ })
+})
\ No newline at end of file
diff --git a/javascripts/src/components/common/selectable_table_row/SelectableTableRow.tsx b/javascripts/src/components/common/selectable_table_row/SelectableTableRow.tsx
new file mode 100644
index 00000000..dc2e28d7
--- /dev/null
+++ b/javascripts/src/components/common/selectable_table_row/SelectableTableRow.tsx
@@ -0,0 +1,31 @@
+// License: LGPL-3.0-or-later
+import * as React from 'react';
+import { TableRowSelectHandlerProvider } from './connect';
+
+export interface SelectableTableRowProps
+ {/**
+ * Action you want to take when the row is selected
+ * @memberof SelectableTableRowProps
+ */
+ onSelect: () => void
+}
+
+/**
+ * So you want a table row that fires an action when any part of the row is clicked. Well that's what the SelectableTableRow does for you. Is it Aria compatible? Not yet!
+ * @class SelectableTableRow
+ * @extends React.Component
+ */
+class SelectableTableRow extends React.Component {
+ render() {
+ return
+
+ {this.props.children}
+
+ ;
+ }
+}
+
+export default SelectableTableRow
+
+
+
diff --git a/javascripts/src/components/common/selectable_table_row/connect.tsx b/javascripts/src/components/common/selectable_table_row/connect.tsx
new file mode 100644
index 00000000..a5423a9a
--- /dev/null
+++ b/javascripts/src/components/common/selectable_table_row/connect.tsx
@@ -0,0 +1,59 @@
+//License: LGPL-3.0-or-later
+//https://github.com/jaredpalmer/formik/blob/master/src/connect.tsx
+import React = require("react");
+import hoistNonReactStatics = require('hoist-non-react-statics');
+
+
+/**
+ * Passed via provider to children of the SelectableTableRow
+ * @interface TableRowSelectHandler
+ */
+export interface TableRowSelectHandlerContext {
+
+ /**
+ * Action to take on selection. A child of SelectableTableRow needs this
+ * because there needs to be a focusable element for keyboard users to use
+ * @memberof TableRowSelectHandler
+ */
+ selectHandler: {
+ onSelect: () => void
+ }
+}
+
+export const {
+ Provider: TableRowSelectHandlerProvider,
+ Consumer: TableRowSelectHandlerConsumer,
+ } = React.createContext<{
+ onSelect: () => void
+ }>({} as any);
+
+
+/**
+ * Connect any component to Formik context, and inject as a prop called `modal`;
+ * @param Comp React Component
+ */
+export function connectTableRowSelectHandler(
+ Comp: React.ComponentType
+ ) {
+ const C: React.SFC = (props: OuterProps) => (
+
+ {selectHandler => }
+
+ );
+ const componentDisplayName =
+ Comp.displayName ||
+ 'Component';
+
+ // Assign Comp to C.WrappedComponent so we can access the inner component in tests
+ // For example, gets us
+ (C as React.SFC & {
+ WrappedComponent: React.ReactNode;
+ }).WrappedComponent = Comp;
+
+ C.displayName = `TableRowSelectHandlerConnect(${componentDisplayName})`;
+
+ return hoistNonReactStatics(
+ C,
+ Comp as React.ComponentClass // cast type to ComponentClass (even if SFC)
+ );
+ }
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 903db1be..9f311b28 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5026,6 +5026,16 @@
"es6-promise": "*"
}
},
+ "@types/hoist-non-react-statics": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+ "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
"@types/istanbul-lib-coverage": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.0.tgz",
@@ -13663,12 +13673,6 @@
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
"dev": true
},
- "lodash.clonedeep": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
- "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
- "dev": true
- },
"lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
@@ -13738,12 +13742,6 @@
"integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=",
"dev": true
},
- "lodash.mergewith": {
- "version": "4.6.1",
- "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
- "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==",
- "dev": true
- },
"lodash.restparam": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz",
@@ -14219,7 +14217,8 @@
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
- "dev": true
+ "dev": true,
+ "optional": true
},
"nanomatch": {
"version": "1.2.9",
@@ -14478,9 +14477,9 @@
}
},
"node-sass": {
- "version": "4.11.0",
- "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz",
- "integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==",
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz",
+ "integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==",
"dev": true,
"requires": {
"async-foreach": "^0.1.3",
@@ -14490,18 +14489,24 @@
"get-stdin": "^4.0.1",
"glob": "^7.0.3",
"in-publish": "^2.0.0",
- "lodash.assign": "^4.2.0",
- "lodash.clonedeep": "^4.3.2",
- "lodash.mergewith": "^4.6.0",
+ "lodash": "^4.17.11",
"meow": "^3.7.0",
"mkdirp": "^0.5.1",
- "nan": "^2.10.0",
+ "nan": "^2.13.2",
"node-gyp": "^3.8.0",
"npmlog": "^4.0.0",
"request": "^2.88.0",
"sass-graph": "^2.2.4",
"stdout-stream": "^1.4.0",
"true-case-path": "^1.0.2"
+ },
+ "dependencies": {
+ "nan": {
+ "version": "2.13.2",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz",
+ "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==",
+ "dev": true
+ }
}
},
"nopt": {
diff --git a/package.json b/package.json
index a4cb5c71..aeb0a4d4 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"@types/enzyme": "^3.1.9",
"@types/enzyme-to-json": "^1.5.1",
"@types/es6-promise": "^3.3.0",
+ "@types/hoist-non-react-statics": "^3.3.1",
"@types/jest": "^22.2.2",
"@types/jquery": "^3.3.1",
"@types/jsdom": "^11.0.4",
@@ -65,7 +66,7 @@
"less": "^3.0.4",
"less-loader": "^4.1.0",
"lodash": "^4.17.11",
- "node-sass": "^4.11.0",
+ "node-sass": "^4.12.0",
"phantomjs-prebuilt": "^2.1.16",
"postcss-cssnext": "^2.9.0",
"postcss-import": "^9.1.0",
@@ -110,6 +111,7 @@
"focus-group": "^0.3.1",
"form-serialize": "0.7.0",
"format-number": "2.0.2",
+ "hoist-non-react-statics": "^3.3.0",
"i18n-js": "^3.0.3",
"iban": "0.0.8",
"imagesloaded": "4.1.1",