├── .gitattributes
├── src
├── plugins
│ ├── local
│ │ ├── actions
│ │ │ └── index.js
│ │ ├── index.js
│ │ ├── components
│ │ │ ├── TableHeadingCellContainer.js
│ │ │ ├── index.js
│ │ │ ├── NextButtonContainer.js
│ │ │ ├── PreviousButtonContainer.js
│ │ │ ├── PageDropdownContainer.js
│ │ │ ├── TableContainer.js
│ │ │ ├── TableBodyContainer.js
│ │ │ ├── TableHeadingContainer.js
│ │ │ └── RowContainer.js
│ │ ├── reducers
│ │ │ ├── index.js
│ │ │ └── __tests__
│ │ │ │ └── localReducerTests.js
│ │ └── selectors
│ │ │ └── localSelectors.js
│ ├── position
│ │ ├── constants
│ │ │ └── index.js
│ │ ├── components
│ │ │ ├── Pagination.js
│ │ │ ├── index.js
│ │ │ ├── TableBody.js
│ │ │ ├── SpacerRow.js
│ │ │ └── TableEnhancer.js
│ │ ├── actions
│ │ │ └── index.js
│ │ ├── index.js
│ │ ├── selectors
│ │ │ ├── __tests__
│ │ │ │ └── indexTest.js
│ │ │ └── index.js
│ │ ├── initial-state.js
│ │ ├── reducers
│ │ │ ├── index.js
│ │ │ └── __tests__
│ │ │ │ └── indexTest.js
│ │ └── utils.js
│ └── legacyStyle
│ │ └── index.js
├── components
│ ├── TableHeadingCellEnhancer.js
│ ├── Test.js
│ ├── Loading.js
│ ├── NoResults.js
│ ├── NextButton.js
│ ├── PreviousButton.js
│ ├── TableBody.js
│ ├── SettingsToggle.js
│ ├── Layout.js
│ ├── Cell.js
│ ├── Pagination.js
│ ├── TableHeadingCell.js
│ ├── Settings.js
│ ├── Table.js
│ ├── TableHeading.js
│ ├── SettingsWrapper.js
│ ├── NextButtonContainer.js
│ ├── Row.js
│ ├── NextButtonEnhancer.js
│ ├── PageDropdownEnhancer.js
│ ├── PreviousButtonEnhancer.js
│ ├── FilterEnhancer.js
│ ├── PreviousButtonContainer.js
│ ├── FilterContainer.js
│ ├── Filter.js
│ ├── PageDropdownContainer.js
│ ├── SettingsToggleContainer.js
│ ├── LoadingContainer.js
│ ├── NoResultsContainer.js
│ ├── __tests__
│ │ ├── SettingsToggleTest.js
│ │ ├── FilterTest.js
│ │ ├── TableBodyTest.js
│ │ ├── RowTest.js
│ │ ├── NextButtonTest.js
│ │ ├── CellTest.js
│ │ ├── SettingsTest.js
│ │ ├── PaginationTest.js
│ │ ├── PreviousButtonTest.js
│ │ ├── TableTest.js
│ │ ├── TableHeadingTest.js
│ │ ├── TableHeadingCellTest.js
│ │ ├── SettingsWrapperTest.js
│ │ └── PageDropdownTest.js
│ ├── RowDefinition.js
│ ├── PaginationContainer.js
│ ├── SettingsWrapperContainer.js
│ ├── LayoutContainer.js
│ ├── TableHeadingContainer.js
│ ├── TableBodyContainer.js
│ ├── PageDropdown.js
│ ├── TableContainer.js
│ ├── RowContainer.js
│ ├── SettingsContainer.js
│ ├── ColumnDefinition.js
│ ├── CellContainer.js
│ ├── TableHeadingCellContainer.js
│ └── index.js
├── utils
│ ├── valueUtils.js
│ ├── index.js
│ ├── rowUtils.js
│ ├── __tests__
│ │ ├── griddleConnectTest.js
│ │ ├── sortUtilsTests.js
│ │ ├── rowUtilsTests.js
│ │ ├── dataUtilsTests.js
│ │ ├── columnUtilsTests.js
│ │ └── initilizerTests.js
│ ├── griddleConnect.js
│ ├── listenerUtils.js
│ ├── columnUtils.js
│ ├── sortUtils.js
│ ├── initializer.js
│ └── dataUtils.js
├── settingsComponentObjects
│ ├── index.js
│ ├── PageSizeSettings.js
│ └── ColumnChooser.js
├── core
│ ├── __tests__
│ │ └── corePluginTests.js
│ ├── index.js
│ └── initialState.js
├── module.js
├── constants
│ └── index.js
├── actions
│ └── index.js
├── index.js
├── reducers
│ ├── dataReducer.js
│ └── __tests__
│ │ └── dataReducerTest.js
└── selectors
│ ├── __tests__
│ └── dataSelectorsTest.js
│ └── dataSelectors.js
├── .vscode
└── settings.json
├── .prettierrc
├── .eslintrc
├── .editorconfig
├── .jshintrc
├── test
└── helpers
│ └── setupTest.js
├── .github
├── PULL_REQUEST_TEMPLATE.md
└── ISSUE_TEMPLATE.md
├── .storybook
├── config.js
├── webpack.config.js
└── webpack-build.config.js
├── .travis.yml
├── stories
├── fakeData.d.ts
├── fakeData2.d.ts
└── fakeData2.js
├── tsconfig.json
├── .babelrc
├── .npmignore
├── .gitignore
├── LICENSE
├── webpack.config.js
├── conduct.md
├── README.md
├── CHANGELOG.md
└── package.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/src/plugins/local/actions/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true
3 | }
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "arrowParens": "always"
4 | }
5 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "rules": {
4 | "comma-dangle": 0
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/plugins/position/constants/index.js:
--------------------------------------------------------------------------------
1 | export const XY_POSITION_CHANGED = 'XY_POSITION_CHANGED';
2 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | insert_final_newline = true
7 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esnext": true,
3 | "asi": true,
4 | "eqeqeq": "cantbeturnedoff",
5 | "eqnull": true,
6 | "sub":true
7 | }
8 |
--------------------------------------------------------------------------------
/test/helpers/setupTest.js:
--------------------------------------------------------------------------------
1 | import { configure } from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 |
4 | configure({ adapter: new Adapter() });
5 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Griddle major version
2 |
3 | ## Changes proposed in this pull request
4 |
5 | ## Why these changes are made
6 |
7 | ## Are there tests?
--------------------------------------------------------------------------------
/src/components/TableHeadingCellEnhancer.js:
--------------------------------------------------------------------------------
1 | // Obsolete
2 | const EnhancedHeadingCell = OriginalComponent => OriginalComponent;
3 |
4 | export default EnhancedHeadingCell;
5 |
--------------------------------------------------------------------------------
/src/utils/valueUtils.js:
--------------------------------------------------------------------------------
1 | export function valueOrResult(arg, ...args) {
2 | if (typeof arg === 'function') {
3 | return arg.apply(null, args);
4 | }
5 | return arg;
6 | }
7 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from '@storybook/react';
2 |
3 | function loadStories() {
4 | require('../stories/index.tsx');
5 | }
6 |
7 | configure(loadStories, module);
8 |
--------------------------------------------------------------------------------
/src/components/Test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class Test extends React.Component {
4 | render() {
5 | return
Hi from component
;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/Loading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Loading = ({ className, style }) => (
4 |
5 | Loading…
6 |
7 | );
8 |
9 | export default Loading;
10 |
--------------------------------------------------------------------------------
/src/components/NoResults.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NoResults = ({ className, style }) => (
4 |
5 | No results found.
6 |
7 | );
8 |
9 | export default NoResults;
10 |
--------------------------------------------------------------------------------
/src/plugins/local/index.js:
--------------------------------------------------------------------------------
1 | import components from './components';
2 | import * as reducer from './reducers';
3 | import * as selectors from './selectors/localSelectors';
4 |
5 | export default {
6 | components,
7 | reducer,
8 | selectors
9 | };
--------------------------------------------------------------------------------
/src/plugins/position/components/Pagination.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // We're not going to be displaying a pagination bar for infinite scrolling.
4 | const PaginationComponent = (props) => ;
5 |
6 | export default PaginationComponent;
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '9'
4 |
5 | branches:
6 | only:
7 | - master
8 |
9 | install:
10 | - 'npm install'
11 | script:
12 | - npm run build
13 | - npm run check-ts
14 | - npm run build-examples
15 | - npm test
16 |
--------------------------------------------------------------------------------
/src/plugins/local/components/TableHeadingCellContainer.js:
--------------------------------------------------------------------------------
1 | import TableHeadingCellContainer from '../../../components/TableHeadingCellContainer';
2 |
3 | // Obsolete
4 | const EnhancedHeadingCell = TableHeadingCellContainer;
5 |
6 | export default EnhancedHeadingCell;
7 |
--------------------------------------------------------------------------------
/src/components/NextButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NextButton = ({ hasNext, onClick, style, className, text }) => hasNext ? (
4 |
5 | ) :
6 | null;
7 |
8 | export default NextButton;
9 |
--------------------------------------------------------------------------------
/src/components/PreviousButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const PreviousButton = ({ hasPrevious, onClick, style, className, text }) => hasPrevious ? (
4 |
5 | ) :
6 | null;
7 |
8 | export default PreviousButton;
9 |
--------------------------------------------------------------------------------
/stories/fakeData.d.ts:
--------------------------------------------------------------------------------
1 | export interface FakeData {
2 | id: number;
3 | name: string;
4 | city: string;
5 | state: string;
6 | country: string;
7 | company: string;
8 | favoriteNumber: number;
9 | }
10 |
11 | declare const fakeData: FakeData[];
12 |
13 | export default fakeData;
14 |
--------------------------------------------------------------------------------
/stories/fakeData2.d.ts:
--------------------------------------------------------------------------------
1 | import { FakeData } from "./fakeData";
2 |
3 | declare class person {
4 | constructor(data: FakeData);
5 | }
6 |
7 | declare class personClass {
8 | constructor(data: FakeData);
9 | }
10 |
11 | declare const fakeData2: person[];
12 | declare const fakeData3: personClass[];
13 |
--------------------------------------------------------------------------------
/src/components/TableBody.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const TableBody = ({ rowIds, Row, style, className }) => (
4 |
5 | { rowIds && rowIds.map((k, i) =>
) }
6 |
7 | );
8 |
9 | export default TableBody;
10 |
--------------------------------------------------------------------------------
/src/plugins/position/components/index.js:
--------------------------------------------------------------------------------
1 | import Pagination from './Pagination';
2 | import SpacerRow from './SpacerRow';
3 | import TableBody from './TableBody';
4 | import TableEnhancer from './TableEnhancer';
5 |
6 | export default {
7 | Pagination,
8 | SpacerRow,
9 | TableBody,
10 | TableEnhancer,
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/SettingsToggle.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const SettingsToggle = ({onClick, text, style, className}) => (
4 |
12 | );
13 |
14 | export default SettingsToggle;
15 |
--------------------------------------------------------------------------------
/src/settingsComponentObjects/index.js:
--------------------------------------------------------------------------------
1 | import PageSizeSettings from './PageSizeSettings';
2 | import ColumnChooser from './ColumnChooser';
3 |
4 | export const components = {
5 | pageSizeSettings: PageSizeSettings,
6 | columnChooser: ColumnChooser,
7 | };
8 |
9 | export default {
10 | pageSizeSettings: { order: 1 },
11 | columnChooser: { order: 2 },
12 | };
13 |
--------------------------------------------------------------------------------
/src/components/Layout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const component = ({Table, Pagination, Filter, SettingsWrapper, Style, className, style}) => (
4 |
5 | {Style && }
6 |
7 |
8 |
9 |
10 |
11 | )
12 |
13 | export default component;
14 |
--------------------------------------------------------------------------------
/src/components/Cell.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Cell = ({ value, onClick, onMouseEnter, onMouseLeave, style, className }) => (
4 |
11 | {value}
12 | |
13 | );
14 |
15 | export default Cell;
16 |
--------------------------------------------------------------------------------
/src/plugins/position/components/TableBody.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SpacerRow from './SpacerRow';
3 |
4 | const TableBody = ({ rowIds, Row }) => (
5 |
6 |
7 | { rowIds && rowIds.map(r =>
) }
8 |
9 |
10 | );
11 |
12 | export default TableBody;
13 |
--------------------------------------------------------------------------------
/src/components/Pagination.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Pagination = ({
4 | Next,
5 | Previous,
6 | PageDropdown,
7 | style,
8 | className }) => (
9 |
10 | {Previous &&
}
11 | {PageDropdown &&
}
12 | {Next &&
}
13 |
14 | );
15 |
16 | export default Pagination;
17 |
--------------------------------------------------------------------------------
/src/plugins/position/actions/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | XY_POSITION_CHANGED,
3 | } from '../constants';
4 |
5 | export function setScrollPosition(xScrollPosition, xScrollMax, xVisible, yScrollPosition, yScrollMax, yVisible) {
6 | return {
7 | type: XY_POSITION_CHANGED,
8 | xScrollPosition,
9 | xScrollMax,
10 | xVisible,
11 | yScrollPosition,
12 | yScrollMax,
13 | yVisible
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/TableHeadingCell.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const TableHeadingCell = ({ title, columnId, onClick, onMouseEnter, onMouseLeave, icon, style, className }) => (
4 |
11 | {title}
12 | |
13 | )
14 |
15 | export default TableHeadingCell;
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Griddle version
2 |
3 | ## Expected Behavior
4 |
5 | ## Actual Behavior
6 |
7 | ## Steps to reproduce
8 |
9 | ## Pull request with failing test or storybook story with issue
10 |
11 | While this step is not necessary, a failing test(s) and/or a [storybook story](https://github.com/storybooks/react-storybook) will help us resolve the issue much more easily. Please see the README for more information.
12 |
13 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | import * as columnUtils from './columnUtils';
2 | import * as compositionUtils from './compositionUtils';
3 | import * as dataUtils from './dataUtils';
4 | import * as rowUtils from './rowUtils';
5 | import * as sortUtils from './sortUtils';
6 | import { connect } from './griddleConnect';
7 |
8 | export default {
9 | columnUtils,
10 | compositionUtils,
11 | dataUtils,
12 | rowUtils,
13 | sortUtils,
14 | connect
15 | };
16 |
--------------------------------------------------------------------------------
/src/components/Settings.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // This is responsible for rendering the individual settings sections
4 | const Settings = ({ settingsComponents, style, className }) => (
5 |
6 | {settingsComponents && settingsComponents.map((SettingsComponent, i) => SettingsComponent &&
)}
7 |
8 | )
9 |
10 | export default Settings;
11 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | module.exports = (baseConfig, env, config) => {
3 | config.module.rules.push({
4 | test: /\.(ts|tsx)$/,
5 | use: [
6 | { loader: 'babel-loader' },
7 | {
8 | loader: 'awesome-typescript-loader'
9 | },
10 | {
11 | loader: 'react-docgen-typescript-loader'
12 | }
13 | ]
14 | });
15 | config.resolve.extensions.push('.ts', '.tsx');
16 | return config;
17 | };
18 |
--------------------------------------------------------------------------------
/src/components/Table.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const Table = ({ TableHeading, TableBody, Loading, NoResults, style, className, dataLoading, visibleRows }) =>
4 | dataLoading ? (Loading && ) :
5 | visibleRows > 0 ?
6 | (
7 |
8 | { TableHeading && }
9 | { TableBody && }
10 |
11 | ) :
12 | (
13 | NoResults &&
14 | );
15 |
16 | export default Table;
17 |
--------------------------------------------------------------------------------
/src/core/__tests__/corePluginTests.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 |
3 | import corePlugin from '../';
4 |
5 | test('has expected keys', test => {
6 | test.truthy(corePlugin.components);
7 | test.truthy(corePlugin.settingsComponentObjects);
8 | test.truthy(corePlugin.reducer);
9 | test.truthy(corePlugin.selectors);
10 | test.truthy(corePlugin.actions);
11 |
12 | test.truthy(corePlugin.pageProperties);
13 | test.truthy(corePlugin.styleConfig);
14 | test.truthy(corePlugin.textProperties);
15 | });
16 |
--------------------------------------------------------------------------------
/src/components/TableHeading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const TableHeading = ({ columnTitles, columnIds, TableHeadingCell, style, className }) => {
4 | const headingCells = columnIds && columnTitles && columnTitles.map((t, i) => );
5 |
6 | return (
7 |
8 |
9 | { headingCells }
10 |
11 |
12 | );
13 | }
14 |
15 | export default TableHeading;
16 |
--------------------------------------------------------------------------------
/src/plugins/position/index.js:
--------------------------------------------------------------------------------
1 | import components from './components';
2 | import * as reducer from './reducers';
3 | import initialState from './initial-state';
4 | import * as selectors from './selectors';
5 |
6 | const PositionPlugin = (config) => {
7 | return {
8 | initialState: {
9 | ...initialState,
10 | positionSettings: Object.assign({}, initialState.positionSettings, config),
11 | },
12 | components,
13 | reducer,
14 | selectors,
15 | };
16 | }
17 |
18 | export default PositionPlugin;
19 |
--------------------------------------------------------------------------------
/src/plugins/position/selectors/__tests__/indexTest.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import Immutable from 'immutable';
3 |
4 | import {
5 | visibleRecordCountSelector
6 | } from '../index';
7 |
8 | test('visible record count selector', test => {
9 | const state = new Immutable.fromJS({
10 | positionSettings: {
11 | rowHeight: 50,
12 | height: 600
13 | },
14 | currentPosition: {
15 | height: 600
16 | },
17 | });
18 |
19 | test.is(visibleRecordCountSelector(state), 12);
20 | });
21 |
22 |
--------------------------------------------------------------------------------
/src/core/index.js:
--------------------------------------------------------------------------------
1 | import components from '../components';
2 | import * as reducer from '../reducers/dataReducer';
3 | import * as selectors from '../selectors/dataSelectors';
4 | import * as actions from '../actions';
5 | import settingsComponentObjects from '../settingsComponentObjects';
6 | import initialState from './initialState';
7 |
8 | const CorePlugin = {
9 | components,
10 | settingsComponentObjects,
11 | reducer,
12 | selectors,
13 | actions,
14 | ...initialState,
15 | };
16 |
17 | export default CorePlugin;
18 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es5",
5 | "lib": ["es5", "es6", "es7", "es2017", "dom"],
6 | "allowJs": true,
7 | "jsx": "preserve",
8 | "noImplicitAny": false,
9 | "noImplicitThis": false,
10 | "strictNullChecks": false,
11 | "types": [],
12 | "noEmit": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "allowSyntheticDefaultImports": true
15 | },
16 | "files": ["stories/index.tsx"],
17 | "exclude": ["node_modules"]
18 | }
19 |
--------------------------------------------------------------------------------
/src/utils/rowUtils.js:
--------------------------------------------------------------------------------
1 | /** Gets a row properties object from a rowProperties component
2 | * @param {Object} rowPropertiesComponent - A react component that contains rowProperties as props
3 | */
4 | export function getRowProperties(rowPropertiesComponent) {
5 | if (!rowPropertiesComponent) return null;
6 |
7 | let rowProps = Object.assign({}, rowPropertiesComponent.props);
8 | delete rowProps.children;
9 |
10 | if (!rowProps.hasOwnProperty('childColumnName')) {
11 | rowProps.childColumnName = 'children';
12 | }
13 |
14 | return rowProps;
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/SettingsWrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // This is a component that wraps all of the other settings components ( SettingsToggle, Settings, etc).
4 | // All of the settings views will be hiddne if isEnabled = false
5 | const SettingsWrapper = ({ SettingsToggle, Settings, isEnabled, isVisible, style, className }) => (
6 | isEnabled ? (
7 |
8 | { SettingsToggle && }
9 | { isVisible && }
10 |
11 | ) : null
12 | )
13 |
14 | export default SettingsWrapper;
15 |
--------------------------------------------------------------------------------
/src/components/NextButtonContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from '../utils/griddleConnect';
3 |
4 | import { textSelector, hasNextSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
5 |
6 | const enhance = OriginalComponent => connect((state, props) => ({
7 | text: textSelector(state, { key: 'next' }),
8 | hasNext: hasNextSelector(state, props),
9 | className: classNamesForComponentSelector(state, 'NextButton'),
10 | style: stylesForComponentSelector(state, 'NextButton'),
11 | }))((props) => );
12 |
13 | export default enhance;
14 |
--------------------------------------------------------------------------------
/src/components/Row.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Row = ({ Cell, griddleKey, columnIds, onClick, onMouseEnter, onMouseLeave, style, className }) => (
4 |
12 | { columnIds && columnIds.map(c => (
13 | |
20 | ))}
21 |
22 | );
23 |
24 | export default Row;
25 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "development": {
4 | "presets": ["@babel/preset-env", "@babel/preset-react"],
5 | "plugins": ["@babel/plugin-proposal-class-properties"]
6 | },
7 | "build": {
8 | "plugins": ["lodash", "@babel/plugin-proposal-class-properties"],
9 | "presets": ["@babel/preset-env", "@babel/preset-react"],
10 | "ignore": ["**/__tests__/*.js", "**/fake-*"]
11 | },
12 | "test": {
13 | "presets": ["@babel/preset-env", "@babel/preset-react"],
14 | "plugins": ["@babel/plugin-proposal-class-properties"],
15 | "only": ["./**/*.js", "node_modules/jest-runtime"]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/NextButtonEnhancer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import compose from 'recompose/compose';
4 | import mapProps from 'recompose/mapProps';
5 | import getContext from 'recompose/getContext';
6 | import { combineHandlers } from '../utils/compositionUtils';
7 |
8 | const enhance = OriginalComponent => compose(
9 | getContext({
10 | events: PropTypes.object
11 | }),
12 | mapProps(({ events: { onNext }, ...props }) => ({
13 | ...props,
14 | onClick: combineHandlers([onNext, props.onClick]),
15 | }))
16 | )((props) => );
17 |
18 | export default enhance;
19 |
--------------------------------------------------------------------------------
/src/components/PageDropdownEnhancer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import compose from 'recompose/compose';
4 | import mapProps from 'recompose/mapProps';
5 | import getContext from 'recompose/getContext';
6 | import { combineHandlers } from '../utils/compositionUtils';
7 |
8 | const enhance = OriginalComponent => compose(
9 | getContext({
10 | events: PropTypes.object
11 | }),
12 | mapProps(({ events: { onGetPage }, ...props }) => ({
13 | ...props,
14 | setPage: combineHandlers([onGetPage, props.setPage]),
15 | }))
16 | )((props) => );
17 |
18 | export default enhance;
19 |
--------------------------------------------------------------------------------
/src/components/PreviousButtonEnhancer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import compose from 'recompose/compose';
4 | import mapProps from 'recompose/mapProps';
5 | import getContext from 'recompose/getContext';
6 | import { combineHandlers } from '../utils/compositionUtils';
7 |
8 | const enhance = OriginalComponent => compose(
9 | getContext({
10 | events: PropTypes.object
11 | }),
12 | mapProps(({ events: { onPrevious }, ...props }) => ({
13 | ...props,
14 | onClick: combineHandlers([onPrevious, props.onClick]),
15 | }))
16 | )((props) => );
17 |
18 | export default enhance;
19 |
--------------------------------------------------------------------------------
/src/components/FilterEnhancer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import compose from 'recompose/compose';
4 | import mapProps from 'recompose/mapProps';
5 | import getContext from 'recompose/getContext';
6 | import { combineHandlers } from '../utils/compositionUtils';
7 |
8 | const EnhancedFilter = OriginalComponent => compose(
9 | getContext({
10 | events: PropTypes.object
11 | }),
12 | mapProps(({ events: { onFilter }, ...props }) => ({
13 | ...props,
14 | setFilter: combineHandlers([onFilter, props.setFilter]),
15 | }))
16 | )(props => );
17 |
18 | export default EnhancedFilter;
19 |
--------------------------------------------------------------------------------
/src/components/PreviousButtonContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from '../utils/griddleConnect';
3 | import { textSelector, hasPreviousSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
4 |
5 | const enhance = OriginalComponent => connect((state, props) => ({
6 | text: textSelector(state, { key: 'previous' }),
7 | hasPrevious: hasPreviousSelector(state, props),
8 | className: classNamesForComponentSelector(state, 'PreviousButton'),
9 | style: stylesForComponentSelector(state, 'PreviousButton'),
10 | }))((props) => );
11 |
12 | export default enhance;
13 |
--------------------------------------------------------------------------------
/src/plugins/local/components/index.js:
--------------------------------------------------------------------------------
1 | import TableBodyContainer from './TableBodyContainer';
2 | import RowContainer from './RowContainer';
3 | import NextButtonContainer from './NextButtonContainer';
4 | import PreviousButtonContainer from './PreviousButtonContainer';
5 | import PageDropdownContainer from './PageDropdownContainer';
6 | import TableContainer from './TableContainer';
7 | import TableHeadingCellContainer from './TableHeadingCellContainer';
8 |
9 | export default {
10 | TableBodyContainer,
11 | RowContainer,
12 | NextButtonContainer,
13 | PreviousButtonContainer,
14 | PageDropdownContainer,
15 | TableContainer,
16 | TableHeadingCellContainer, // TODO: Obsolete; remove
17 | };
18 |
--------------------------------------------------------------------------------
/src/components/FilterContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from '../utils/griddleConnect';
4 |
5 | import { classNamesForComponentSelector, stylesForComponentSelector, textSelector } from '../selectors/dataSelectors';
6 | import { setFilter } from '../actions';
7 |
8 | const EnhancedFilter = OriginalComponent => connect((state, props) => ({
9 | placeholder: textSelector(state, { key: 'filterPlaceholder' }),
10 | className: classNamesForComponentSelector(state, 'Filter'),
11 | style: stylesForComponentSelector(state, 'Filter'),
12 | }), { setFilter })(props => );
13 |
14 | export default EnhancedFilter;
15 |
--------------------------------------------------------------------------------
/src/components/Filter.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class Filter extends Component {
5 | static propTypes = {
6 | setFilter: PropTypes.func,
7 | style: PropTypes.object,
8 | className: PropTypes.string,
9 | placeholder: PropTypes.string,
10 | }
11 |
12 | setFilter = (e) => {
13 | this.props.setFilter(e.target.value);
14 | }
15 |
16 | render() {
17 | return (
18 |
26 | )
27 | }
28 | }
29 |
30 | export default Filter;
31 |
--------------------------------------------------------------------------------
/src/plugins/local/components/NextButtonContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from '../../../utils/griddleConnect';
3 |
4 | import { textSelector, hasNextSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/localSelectors';
5 | import { getNext } from '../../../actions';
6 |
7 | const enhance = OriginalComponent => connect(state => ({
8 | text: textSelector(state, { key: 'next' }),
9 | hasNext: hasNextSelector(state),
10 | className: classNamesForComponentSelector(state, 'NextButton'),
11 | style: stylesForComponentSelector(state, 'NextButton'),
12 | }),
13 | {
14 | getNext
15 | }
16 | )(props => );
17 |
18 | export default enhance;
19 |
--------------------------------------------------------------------------------
/src/components/PageDropdownContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from '../utils/griddleConnect';
4 | import compose from 'recompose/compose';
5 | import mapProps from 'recompose/mapProps';
6 | import getContext from 'recompose/getContext';
7 | import { currentPageSelector, maxPageSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
8 |
9 | const enhance = (
10 | connect((state, props) => ({
11 | maxPages: maxPageSelector(state, props),
12 | currentPage: currentPageSelector(state, props),
13 | className: classNamesForComponentSelector(state, 'PageDropdown'),
14 | style: stylesForComponentSelector(state, 'PageDropdown'),
15 | }))
16 | );
17 |
18 | export default enhance;
19 |
--------------------------------------------------------------------------------
/src/plugins/local/components/PreviousButtonContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from '../../../utils/griddleConnect';
3 |
4 | import { textSelector, hasPreviousSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/localSelectors';
5 | import { getPrevious } from '../../../actions';
6 |
7 | const enhance = OriginalComponent => connect(state => ({
8 | text: textSelector(state, { key: 'previous' }),
9 | hasPrevious: hasPreviousSelector(state),
10 | className: classNamesForComponentSelector(state, 'PreviousButton'),
11 | style: stylesForComponentSelector(state, 'PreviousButton'),
12 | }),
13 | {
14 | getPrevious
15 | }
16 | )(props => );
17 |
18 | export default enhance;
19 |
--------------------------------------------------------------------------------
/src/plugins/local/components/PageDropdownContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from '../../../utils/griddleConnect';
3 | import { createStructuredSelector } from 'reselect';
4 |
5 | import { currentPageSelector, maxPageSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/localSelectors';
6 | import { setPage } from '../../../actions';
7 |
8 | const enhance = OriginalComponent => connect(state => ({
9 | maxPages: maxPageSelector(state),
10 | currentPage: currentPageSelector(state),
11 | className: classNamesForComponentSelector(state, 'PageDropdown'),
12 | style: stylesForComponentSelector(state, 'PageDropdown'),
13 | }),
14 | {
15 | setPage
16 | }
17 | )(props => );
18 |
19 | export default enhance;
20 |
21 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Thank you : https://github.com/github/gitignore/blob/master/Node.gitignore
2 |
3 | # source
4 | src
5 |
6 | # Storybook
7 | stories
8 | storybook-static
9 |
10 |
11 | # Logs
12 | logs
13 | *.log
14 |
15 | # Runtime data
16 | pids
17 | *.pid
18 | *.seed
19 |
20 | # Directory for instrumented libs generated by jscoverage/JSCover
21 | lib-cov
22 |
23 | # Coverage directory used by tools like istanbul
24 | coverage
25 |
26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
27 | .grunt
28 |
29 | # Dependency directory
30 | # Commenting this out is preferred by some people, see
31 | # https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
32 | node_modules
33 | modules
34 | docs/html
35 |
36 | # Users Environment Variables
37 | .lock-wscript
38 |
39 | .DS_Store
40 | *.tgz
--------------------------------------------------------------------------------
/src/plugins/position/initial-state.js:
--------------------------------------------------------------------------------
1 | const initialState = {
2 | renderedData: [],
3 | currentPosition: {
4 | height: 500,
5 | width: 500,
6 | xScrollChangePosition: 0,
7 | yScrollChangePosition: 0,
8 | renderedStartDisplayIndex: 0,
9 | renderedEndDisplayIndex: 16,
10 | visibleDataLength: 16
11 | },
12 | positionSettings: {
13 | // The height of the table
14 | tableHeight: '70%',
15 | // The width of the table
16 | tableWidth: null,
17 | // The minimum row height
18 | rowHeight: 30,
19 | // The minimum column width
20 | defaultColumnWidth: null,
21 | // Whether or not the header should be fixed
22 | fixedHeader: true,
23 | // Disable pointer events while scrolling to improve performance
24 | disablePointerEvents: false,
25 | },
26 | };
27 |
28 | export default initialState;
29 |
--------------------------------------------------------------------------------
/src/plugins/position/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { setCurrentPosition, updatePositionProperties } from '../utils';
2 |
3 | export function XY_POSITION_CHANGED(state, action) {
4 | const height = state.getIn(['currentPosition', 'height']) || 0;
5 | const width = state.getIn(['currentPosition', 'width']) || 0;
6 |
7 | return state
8 | .setIn(['currentPosition', 'xScrollChangePosition'], action.xScrollPosition || 0)
9 | .setIn(['currentPosition', 'yScrollChangePosition'], action.yScrollPosition || 0)
10 | .setIn(['currentPosition', 'height'], action.height || height)
11 | .setIn(['currentPosition', 'width'], action.width || width);
12 | }
13 |
14 | export function GRIDDLE_SET_FILTER_AFTER(state, action, helpers) {
15 | return state.setIn(['currentPosition', 'xScrollChangePosition'], 0)
16 | .setIn(['currentPosition', 'yScrollChangePosition'], 0);
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/SettingsToggleContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from '../utils/griddleConnect';
3 | import compose from 'recompose/compose';
4 | import { textSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
5 | import { toggleSettings as toggleSettingsAction } from '../actions';
6 |
7 | const enhancedSettingsToggle = OriginalComponent => compose(
8 | connect((state, props) => ({
9 | text: textSelector(state, { key: 'settingsToggle' }),
10 | className: classNamesForComponentSelector(state, 'SettingsToggle'),
11 | style: stylesForComponentSelector(state, 'SettingsToggle'),
12 | }),
13 | {
14 | toggleSettings: toggleSettingsAction
15 | }
16 | ),
17 | )(props => );
21 |
22 | export default enhancedSettingsToggle;
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Thank you : https://github.com/github/gitignore/blob/master/Node.gitignore
2 |
3 | # Logs
4 | logs
5 | *.log
6 |
7 | # Runtime data
8 | pids
9 | *.pid
10 | *.seed
11 |
12 | # Directory for instrumented libs generated by jscoverage/JSCover
13 | lib-cov
14 |
15 | # Coverage directory used by tools like istanbul
16 | coverage
17 |
18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
19 | .grunt
20 |
21 | # Compiled binary addons (http://nodejs.org/api/addons.html)
22 | build/Release
23 |
24 | # Dependency directory
25 | # Commenting this out is preferred by some people, see
26 | # https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
27 | node_modules
28 | modules
29 | docs/html
30 |
31 | # Users Environment Variables
32 | .lock-wscript
33 |
34 | .DS_Store
35 |
36 | build/
37 | lib/
38 | dist/
39 | storybook-static/
40 |
41 | *.tgz
--------------------------------------------------------------------------------
/src/components/LoadingContainer.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import { connect } from '../utils/griddleConnect';
3 | import compose from 'recompose/compose';
4 | import mapProps from 'recompose/mapProps';
5 | import getContext from 'recompose/getContext';
6 |
7 | import { classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
8 |
9 | const LoadingContainer = compose(
10 | getContext({
11 | components: PropTypes.object,
12 | }),
13 | connect(
14 | state => ({
15 | className: classNamesForComponentSelector(state, 'Loading'),
16 | style: stylesForComponentSelector(state, 'Loading'),
17 | })
18 | ),
19 | mapProps((props) => {
20 | const { components, ...otherProps } = props;
21 | return {
22 | Loading: components.Loading,
23 | ...otherProps
24 | };
25 | })
26 | );
27 |
28 | export default LoadingContainer;
29 |
30 |
--------------------------------------------------------------------------------
/src/module.js:
--------------------------------------------------------------------------------
1 | import Griddle from './index';
2 |
3 | import * as actions from './actions';
4 | import components from './components';
5 | import * as constants from './constants';
6 | import * as selectors from './selectors/dataSelectors';
7 | import settingsComponentObjects from './settingsComponentObjects';
8 | import utils from './utils';
9 |
10 | import CorePlugin from './core';
11 | import LegacyStylePlugin from './plugins/legacyStyle';
12 | import LocalPlugin from './plugins/local';
13 | import PositionPlugin from './plugins/position';
14 |
15 | const plugins = {
16 | CorePlugin,
17 | LegacyStylePlugin,
18 | LocalPlugin,
19 | PositionPlugin
20 | };
21 |
22 | const { ColumnDefinition, RowDefinition } = components;
23 |
24 | const { connect } = utils;
25 |
26 | export default Griddle;
27 | export {
28 | actions,
29 | components,
30 | constants,
31 | selectors,
32 | settingsComponentObjects,
33 | utils,
34 | plugins,
35 | ColumnDefinition,
36 | RowDefinition,
37 | connect
38 | };
39 |
--------------------------------------------------------------------------------
/stories/fakeData2.js:
--------------------------------------------------------------------------------
1 | import fakeData from './fakeData';
2 |
3 | export function person({
4 | id,
5 | name,
6 | city,
7 | state,
8 | country,
9 | company,
10 | favoriteNumber
11 | }) {
12 | const personObject = {};
13 |
14 | personObject.id = id;
15 | personObject.name = name;
16 | personObject.city = city;
17 | personObject.state = state;
18 | personObject.country = country;
19 | personObject.company = company;
20 | personObject.favoriteNumber = favoriteNumber;
21 |
22 | return personObject;
23 | }
24 |
25 | export class personClass {
26 | constructor({ id, name, city, state, country, company, favoriteNumber }) {
27 | this.id = id;
28 | this.name = name;
29 | this.city = city;
30 | this.state = state;
31 | this.country = country;
32 | this.company = company;
33 | this.favoriteNumber = favoriteNumber;
34 | }
35 | }
36 |
37 | export const fakeData2 = fakeData.map(x => new person(x));
38 | export const fakeData3 = fakeData.map(x => new personClass(x));
39 |
--------------------------------------------------------------------------------
/src/components/NoResultsContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from '../utils/griddleConnect';
4 | import compose from 'recompose/compose';
5 | import mapProps from 'recompose/mapProps';
6 | import getContext from 'recompose/getContext';
7 |
8 | import { classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
9 |
10 | const NoResultsContainer = OriginalComponent => compose(
11 | getContext({
12 | components: PropTypes.object,
13 | }),
14 | connect(
15 | state => ({
16 | className: classNamesForComponentSelector(state, 'NoResults'),
17 | style: stylesForComponentSelector(state, 'NoResults'),
18 | })
19 | ),
20 | mapProps((props) => {
21 | const { components, ...otherProps } = props;
22 | return {
23 | NoResults: components.NoResults,
24 | ...otherProps
25 | };
26 | })
27 | )((props) => );
28 |
29 | export default NoResultsContainer;
30 |
31 |
--------------------------------------------------------------------------------
/src/utils/__tests__/griddleConnectTest.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 |
3 | import { mergeConnectParametersWithOptions } from '../griddleConnect';
4 |
5 | test('makes options the fourth parameter even if connectOptions contains only one parameter', assert => {
6 | const mapStateToProps = (state, props) => {};
7 | const connectParams = [mapStateToProps];
8 |
9 | const output = mergeConnectParametersWithOptions(connectParams, { 'test': 'hi' });
10 | assert.deepEqual(
11 | output,
12 | [mapStateToProps, undefined, undefined, { 'test': 'hi' }]
13 | );
14 | });
15 |
16 | test('merges with existing options', assert => {
17 | const mapStateToProps = (state, props) => {};
18 | const action = () => { };
19 | const connectParams = [mapStateToProps, { someAction: action }, undefined, { 'one': 'two'}];
20 |
21 | const output = mergeConnectParametersWithOptions(connectParams, { 'test': 'hi' });
22 | assert.deepEqual(
23 | output,
24 | [mapStateToProps, { someAction: action }, undefined, { 'test': 'hi', 'one': 'two' }]
25 | );
26 | })
27 |
--------------------------------------------------------------------------------
/src/utils/__tests__/sortUtilsTests.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import { fromJS } from 'immutable';
3 |
4 | import { defaultSort } from '../sortUtils';
5 |
6 | const testData = fromJS([
7 | {
8 | name: 'cool2',
9 | location: {
10 | city: 'city2',
11 | }
12 | },
13 | {
14 | name: 'cool1',
15 | location: {
16 | city: 'city1',
17 | }
18 | },
19 | {
20 | name: 'cool3',
21 | location: {
22 | city: 'city0',
23 | }
24 | }
25 | ]);
26 |
27 | test('defaultSort sorts on column value', test => {
28 | const sortedData = defaultSort(testData, 'name');
29 | test.is(sortedData.get('0').get('name'), 'cool1');
30 | });
31 |
32 | test('defaultSort sorts in descending order', test => {
33 | const sortedData = defaultSort(testData, 'name', false);
34 | test.is(sortedData.get('0').get('name'), 'cool3');
35 | });
36 |
37 | test('defaultSort sorts in by nested data', test => {
38 | const sortedData = defaultSort(testData, 'location.city', true);
39 | test.is(sortedData.get('0').get('name'), 'cool3');
40 | });
41 |
--------------------------------------------------------------------------------
/src/components/__tests__/SettingsToggleTest.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import test from 'ava';
3 | import { shallow } from 'enzyme';
4 |
5 | import SettingsToggle from '../SettingsToggle';
6 |
7 | test('renders', (t) => {
8 | const wrapper = shallow();
9 |
10 | t.true(wrapper.matchesElement());
11 | });
12 |
13 | test('renders with style', (t) => {
14 | const style = { backgroundColor: '#EDEDED' };
15 | const wrapper = shallow();
16 |
17 | t.true(wrapper.matchesElement());
18 | });
19 |
20 | test('renders with className', (t) => {
21 | const wrapper = shallow();
22 |
23 | t.true(wrapper.matchesElement());
24 | });
25 |
26 | test('fires on click', (t) => {
27 | let clicked = false;
28 | const onClick = () => { clicked = true; };
29 | const wrapper = shallow();
30 | wrapper.simulate('click');
31 |
32 | t.true(clicked);
33 | });
34 |
--------------------------------------------------------------------------------
/src/plugins/position/reducers/__tests__/indexTest.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import Immutable from 'immutable';
3 |
4 | import {
5 | XY_POSITION_CHANGED
6 | } from '../index';
7 |
8 | test('xy_position_changed sets position to 0 when not available', t => {
9 | const state = new Immutable.Map();
10 | const outputState = XY_POSITION_CHANGED(state, {});
11 |
12 | t.deepEqual(outputState.toJSON(), {
13 | currentPosition: {
14 | xScrollChangePosition: 0,
15 | yScrollChangePosition: 0,
16 | height: 0,
17 | width: 0
18 | }
19 | });
20 | });
21 |
22 | test('xy_position_changed sets position to action information', t => {
23 | const state = new Immutable.Map();
24 | const outputState = XY_POSITION_CHANGED(state, {
25 | yScrollPosition: 10,
26 | xScrollPosition: 20,
27 | height: 30,
28 | width: 40
29 | });
30 |
31 | t.deepEqual(outputState.toJSON(), {
32 | currentPosition: {
33 | xScrollChangePosition: 20,
34 | yScrollChangePosition: 10,
35 | height: 30,
36 | width: 40
37 | }
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/src/components/__tests__/FilterTest.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import test from 'ava';
3 | import { shallow } from 'enzyme';
4 |
5 | import Filter from '../Filter';
6 |
7 | test('renders', (t) => {
8 | const wrapper = shallow();
9 | t.true(wrapper.matchesElement());
10 | });
11 |
12 | test('renders with style', (t) => {
13 | const style = { backgroundColor: '#EDEDED' };
14 | const wrapper = shallow();
15 |
16 | t.true(
17 | wrapper.matchesElement()
18 | );
19 | });
20 |
21 | test('renders with className', (t) => {
22 | const wrapper = shallow();
23 |
24 | t.true(wrapper.matchesElement());
25 | });
26 |
27 | test('calls setFilter on change', (t) => {
28 | let calledSetFilter = false;
29 | const setFilter = (e) => (calledSetFilter = true);
30 | const wrapper = shallow();
31 |
32 | wrapper.simulate('change', { target: { value: 'abc' } });
33 |
34 | t.true(calledSetFilter);
35 | });
36 |
--------------------------------------------------------------------------------
/src/components/RowDefinition.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export default class RowDefinition extends Component {
5 | static propTypes = {
6 | //Children can be either a single column definition or an array
7 | //of column definition objects
8 | //TODO: get this prop type working again
9 | /*children: PropTypes.oneOfType([
10 | PropTypes.instanceOf(ColumnDefinition),
11 | PropTypes.arrayOf(PropTypes.instanceOf(ColumnDefinition))
12 | ]),*/
13 | //The column value that should be used as the key for the row
14 | //if this is not set it will make one up (not efficient)
15 | rowKey: PropTypes.string,
16 |
17 | //The column that will be known used to track child data
18 | //By default this will be "children"
19 | childColumnName: PropTypes.string,
20 |
21 | //The css class name, or a function to generate a class name from props, to apply to this row.
22 | cssClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
23 | }
24 |
25 | render () {
26 | return null;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/PaginationContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from '../utils/griddleConnect';
4 | import compose from 'recompose/compose';
5 | import mapProps from 'recompose/mapProps';
6 | import getContext from 'recompose/getContext';
7 |
8 | import { classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
9 |
10 | const EnhancedPaginationContainer = OriginalComponent => compose(
11 | getContext({
12 | components: PropTypes.object,
13 | }),
14 | connect(
15 | (state, props) => ({
16 | className: classNamesForComponentSelector(state, 'Pagination'),
17 | style: stylesForComponentSelector(state, 'Pagination'),
18 | })
19 | ),
20 | mapProps((props) => {
21 | const { components, ...otherProps } = props;
22 | return {
23 | Next: components.NextButton,
24 | Previous: components.PreviousButton,
25 | PageDropdown: components.PageDropdown,
26 | ...otherProps
27 | };
28 | })
29 | )((props) => );
30 |
31 | export default EnhancedPaginationContainer;
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 DynamicTyped
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.
--------------------------------------------------------------------------------
/src/settingsComponentObjects/PageSizeSettings.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from '../utils/griddleConnect';
3 | import compose from 'recompose/compose';
4 | import withState from 'recompose/withState';
5 | import withHandlers from 'recompose/withHandlers';
6 |
7 | import { pageSizeSelector } from '../selectors/dataSelectors';
8 |
9 | import { setPageSize as setPageSizeAction } from '../actions';
10 |
11 | const ComposedPageSizeSettings = compose(
12 | connect(
13 | (state) => ({
14 | pageSize: pageSizeSelector(state),
15 | }),
16 | {
17 | setPageSize: setPageSizeAction
18 | }
19 | ),
20 | withState('value', 'updateValue', ''),
21 | withHandlers({
22 | onChange: props => e => {
23 | props.updateValue(e.target.value)
24 | },
25 | onSave: props => e => {
26 | props.setPageSize(props.value)
27 | }
28 | }),
29 | )(({ pageSize, onChange, onSave }) => (
30 |
31 |
32 |
33 |
34 | ))
35 |
36 | export default ComposedPageSizeSettings;
37 |
--------------------------------------------------------------------------------
/src/components/SettingsWrapperContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from '../utils/griddleConnect';
4 | import compose from 'recompose/compose';
5 | import mapProps from 'recompose/mapProps';
6 | import getContext from 'recompose/getContext';
7 |
8 | import { isSettingsEnabledSelector, isSettingsVisibleSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
9 |
10 | const EnhancedSettingsWrapper = OriginalComponent => compose(
11 | getContext({
12 | components: PropTypes.object,
13 | }),
14 | mapProps(props => ({
15 | Settings: props.components.Settings,
16 | SettingsToggle: props.components.SettingsToggle
17 | })),
18 | connect((state, props) => ({
19 | isEnabled: isSettingsEnabledSelector(state),
20 | isVisible: isSettingsVisibleSelector(state),
21 | className: classNamesForComponentSelector(state, 'SettingsWrapper'),
22 | style: stylesForComponentSelector(state, 'SettingsWrapper'),
23 | }))
24 | )(props => (
25 |
26 | ));
27 |
28 | export default EnhancedSettingsWrapper;
29 |
--------------------------------------------------------------------------------
/src/components/__tests__/TableBodyTest.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import test from 'ava';
3 | import { shallow } from 'enzyme';
4 |
5 | import TableBody from '../TableBody';
6 |
7 | test('renders with style', (t) => {
8 | const style = { backgroundColor: '#EDEDED' };
9 | const wrapper = shallow();
10 |
11 | t.true(wrapper.matchesElement());
12 | });
13 |
14 | test('renders with className', (t) => {
15 | const wrapper = shallow();
16 |
17 | t.true(wrapper.matchesElement());
18 | });
19 |
20 | test('renders Row for rowIds', (t) => {
21 | const rowIds = [1, 2, 3, 4, 5];
22 | const Row = ({key, griddleKey}) => | {griddleKey} |
;
23 | const wrapper = shallow();
24 |
25 | t.true(wrapper.matchesElement(
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | ));
34 | });
35 |
--------------------------------------------------------------------------------
/src/components/LayoutContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from '../utils/griddleConnect';
4 | import getContext from 'recompose/getContext';
5 | import mapProps from 'recompose/mapProps';
6 | import compose from 'recompose/compose';
7 |
8 | import { classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
9 |
10 | const EnhancedLayout = OriginalComponent => compose(
11 | getContext({
12 | components: PropTypes.object,
13 | }),
14 | connect(
15 | (state, props) => ({
16 | className: classNamesForComponentSelector(state, 'Layout'),
17 | style: stylesForComponentSelector(state, 'Layout'),
18 | })
19 | ),
20 | mapProps( props => ({
21 | Table: props.components.Table,
22 | Pagination: props.components.Pagination,
23 | Filter: props.components.Filter,
24 | SettingsWrapper: props.components.SettingsWrapper,
25 | Style: props.components.Style,
26 | className: props.className,
27 | style: props.style,
28 | })),
29 | )(props => (
30 |
33 | ));
34 |
35 | export default EnhancedLayout;
36 |
--------------------------------------------------------------------------------
/src/components/TableHeadingContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from '../utils/griddleConnect';
4 | import compose from 'recompose/compose';
5 | import mapProps from 'recompose/mapProps';
6 | import getContext from 'recompose/getContext';
7 |
8 | import { columnTitlesSelector, columnIdsSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
9 |
10 | const ComposedContainerComponent = OriginalComponent => compose(
11 | getContext({
12 | components: PropTypes.object,
13 | selectors: PropTypes.object,
14 | }),
15 | connect((state, props) => ({
16 | columnTitles: columnTitlesSelector(state),
17 | columnIds: columnIdsSelector(state),
18 | className: classNamesForComponentSelector(state, 'TableHeading'),
19 | style: stylesForComponentSelector(state, 'TableHeading'),
20 | })),
21 | mapProps(props => {
22 | const { components, ...otherProps } = props;
23 | return {
24 | TableHeadingCell: components.TableHeadingCell,
25 | ...otherProps,
26 | };
27 | })
28 | )(props => (
29 |
30 | ));
31 |
32 | export default ComposedContainerComponent;
33 |
--------------------------------------------------------------------------------
/src/plugins/local/components/TableContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from '../../../utils/griddleConnect';
4 | import compose from 'recompose/compose';
5 | import mapProps from 'recompose/mapProps';
6 | import getContext from 'recompose/getContext';
7 |
8 | import { classNamesForComponentSelector, stylesForComponentSelector, dataLoadingSelector, visibleRowCountSelector } from '../selectors/localSelectors';
9 |
10 | const ComposedContainerComponent = OriginalComponent => compose(
11 | getContext({
12 | components: PropTypes.object
13 | }),
14 | mapProps(props => ({
15 | TableHeading: props.components.TableHeading,
16 | TableBody: props.components.TableBody,
17 | Loading: props.components.Loading,
18 | NoResults: props.components.NoResults,
19 | })),
20 | connect(
21 | (state, props) => ({
22 | dataLoading: dataLoadingSelector(state),
23 | visibleRows: visibleRowCountSelector(state),
24 | className: classNamesForComponentSelector(state, 'Table'),
25 | style: stylesForComponentSelector(state, 'Table'),
26 | })
27 | ),
28 | )(props => );
29 |
30 | export default ComposedContainerComponent;
31 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
2 | const webpack = require('webpack');
3 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
4 | const path = require('path');
5 |
6 | module.exports = {
7 | devtool: 'source-map',
8 | entry: './src/module.js',
9 | output: {
10 | path: path.join(__dirname, '/dist/umd/'),
11 | filename: 'griddle.js',
12 | publicPath: '/build/',
13 | library: 'Griddle',
14 | libraryTarget: 'umd'
15 | },
16 | module: {
17 | rules: [
18 | {
19 | test: /\.jsx?$/,
20 | use: {
21 | loader: 'babel-loader?cacheDirectory',
22 | options: {
23 | presets: ['@babel/preset-env', '@babel/preset-react']
24 | }
25 | },
26 | exclude: ['/node_modules/', '/stories/', '/storybook-static/']
27 | }
28 | ]
29 | },
30 | plugins: [
31 | new LodashModuleReplacementPlugin(),
32 | new webpack.optimize.OccurrenceOrderPlugin(),
33 | new UglifyJsPlugin()
34 | ],
35 | externals: [
36 | {
37 | react: {
38 | root: 'React',
39 | commonjs2: 'react',
40 | commonjs: 'react',
41 | amd: 'react'
42 | }
43 | }
44 | ]
45 | };
46 |
--------------------------------------------------------------------------------
/src/utils/griddleConnect.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 |
5 | /// This method appends options onto existing connect parameters
6 | export const mergeConnectParametersWithOptions = (
7 | originalConnect,
8 | newOptions
9 | ) => {
10 | const [
11 | mapStateFromProps,
12 | mapDispatchFromProps,
13 | mergeProps,
14 | options
15 | ] = originalConnect;
16 |
17 | return [
18 | mapStateFromProps,
19 | mapDispatchFromProps,
20 | mergeProps,
21 | { ...options, ...newOptions }
22 | ];
23 | };
24 |
25 | const griddleConnect = (...connectOptions) => OriginalComponent =>
26 | class extends React.Component {
27 | static contextTypes = {
28 | storeKey: PropTypes.string
29 | };
30 |
31 | constructor(props, context) {
32 | super(props, context);
33 | const newOptions = mergeConnectParametersWithOptions(connectOptions, {
34 | storeKey: context.storeKey
35 | });
36 | this.ConnectedComponent = connect(...newOptions)(OriginalComponent);
37 | }
38 |
39 | render() {
40 | return ;
41 | }
42 | };
43 |
44 | export { griddleConnect as connect };
45 |
--------------------------------------------------------------------------------
/src/constants/index.js:
--------------------------------------------------------------------------------
1 | export const GRIDDLE_SET_FILTER = 'GRIDDLE_SET_FILTER';
2 | export const GRIDDLE_SET_FILTER_BY_COLUMN = 'GRIDDLE_SET_FILTER_BY_COLUMN';
3 | export const GRIDDLE_SET_PAGE = 'GRIDDLE_SET_PAGE';
4 | export const GRIDDLE_FILTER_REMOVED = 'GRIDDLE_FILTER_REMOVED';
5 | export const GRIDDLE_SET_SORT = 'GRIDDLE_SET_SORT';
6 | export const GRIDDLE_LOAD_DATA = 'GRIDDLE_LOAD_DATA';
7 | export const GRIDDLE_LOADED_DATA = 'GRIDDLE_LOADED_DATA';
8 | export const GRIDDLE_NEXT_PAGE = 'GRIDDLE_NEXT_PAGE';
9 | export const GRIDDLE_PREVIOUS_PAGE = 'GRIDDLE_PREVIOUS_PAGE';
10 | export const GRIDDLE_GET_PAGE = 'GRIDDLE_GET_PAGE';
11 | export const GRIDDLE_PAGE_LOADED = 'GRIDDLE_PAGE_LOADED';
12 | export const GRIDDLE_SET_PAGE_SIZE = 'GRIDDLE_SET_PAGE_SIZE';
13 | export const GRIDDLE_INITIALIZE = 'GRIDDLE_INITIALIZE';
14 | export const GRIDDLE_INITIALIZED = 'GRIDDLE_INITIALIZED';
15 | export const GRIDDLE_REMOVED = 'GRIDDLE_REMOVED';
16 | export const GRIDDLE_TOGGLE_COLUMN = 'GRIDDLE_TOGGLE_COLUMN';
17 | export const GRIDDLE_ROW_TOGGLED = 'GRIDDLE_ROW_TOGGLED';
18 | export const GRIDDLE_ROW_SELECTION_TOGGLED = 'GRIDDLE_ROW_SELECTION_TOGGLED';
19 | export const GRIDDLE_TOGGLE_SETTINGS = 'GRIDDLE_TOGGLE_SETTINGS';
20 | export const GRIDDLE_UPDATE_STATE = 'GRIDDLE_UPDATE_STATE';
21 |
--------------------------------------------------------------------------------
/src/components/TableBodyContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from '../utils/griddleConnect';
4 | import compose from 'recompose/compose';
5 | import mapProps from 'recompose/mapProps';
6 | import getContext from 'recompose/getContext';
7 |
8 | import { visibleRowIdsSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
9 |
10 | const ComposedTableBodyContainer = OriginalComponent => compose(
11 | getContext({
12 | components: PropTypes.object,
13 | selectors: PropTypes.object,
14 | }),
15 | connect((state, props) => ({
16 | visibleRowIds: visibleRowIdsSelector(state),
17 | className: classNamesForComponentSelector(state, 'TableBody'),
18 | style: stylesForComponentSelector(state, 'TableBody'),
19 | })),
20 | mapProps(props => {
21 | const { components, ...otherProps } = props;
22 | return {
23 | Row: props.components.Row,
24 | ...otherProps,
25 | };
26 | }),
27 | )(({Row, visibleRowIds, style, className}) => (
28 |
34 | ));
35 |
36 | export default ComposedTableBodyContainer;
37 |
--------------------------------------------------------------------------------
/src/components/PageDropdown.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import isFinite from 'lodash.isfinite';
4 |
5 | /** Gets a range from a single value.
6 | * TODO: Could probably make this take a predicate to avoid running through the loop twice */
7 | const getRange = (number) => {
8 | if (!isFinite(number)) { return [0] }
9 |
10 | return Array(number).fill().map((_, i) => i + 1);
11 | }
12 |
13 | class PageDropdown extends Component {
14 | static propTypes = {
15 | maxPages: PropTypes.number,
16 | currentPage: PropTypes.number,
17 | setPage: PropTypes.func,
18 | style: PropTypes.object,
19 | className: PropTypes.string
20 | }
21 |
22 | setPage = (e) => {
23 | this.props.setPage(parseInt(e.target.value));
24 | }
25 |
26 | render() {
27 | const { currentPage, maxPages } = this.props;
28 |
29 | return (
30 |
41 | );
42 | }
43 | }
44 |
45 | export default PageDropdown;
46 |
--------------------------------------------------------------------------------
/src/components/__tests__/RowTest.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import test from 'ava';
3 | import { shallow } from 'enzyme';
4 |
5 | import Row from '../Row';
6 |
7 | test('renders', (t) => {
8 | const wrapper = shallow(
);
9 | t.true(wrapper.matchesElement(
));
10 | });
11 |
12 | test('renders with griddleKey', (t) => {
13 | const wrapper = shallow(
);
14 | t.true(wrapper.matchesElement(
));
15 | });
16 |
17 | test('renders with style', (t) => {
18 | const style = { backgroundColor: '#EDEDED' };
19 | const wrapper = shallow(
);
20 |
21 | t.true(wrapper.matchesElement(
));
22 | });
23 |
24 | test('renders with className', (t) => {
25 | const className = 'testClass';
26 | const wrapper = shallow(
);
27 |
28 | t.true(wrapper.matchesElement(
));
29 | });
30 |
31 | test('renders column ids', (t) => {
32 | const columnIds = [1, 2, 3];
33 | const cell = props => {props.columnId}
;
34 | const wrapper = shallow(
);
35 |
36 | t.is(wrapper.children().at(0).html(), '1
');
37 | t.is(wrapper.children().at(1).html(), '2
');
38 | t.is(wrapper.children().at(2).html(), '3
');
39 | });
40 |
--------------------------------------------------------------------------------
/src/components/TableContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from '../utils/griddleConnect';
4 | import compose from 'recompose/compose';
5 | import mapProps from 'recompose/mapProps';
6 | import getContext from 'recompose/getContext';
7 |
8 | import { classNamesForComponentSelector, stylesForComponentSelector, dataLoadingSelector, visibleRowCountSelector } from '../selectors/dataSelectors';
9 |
10 | const ComposedContainerComponent = OriginalComponent => compose(
11 | getContext(
12 | {
13 | components: PropTypes.object
14 | }),
15 | //TODO: Should we use withHandlers here instead? I realize that's not 100% the intent of that method
16 | mapProps(props => ({
17 | TableHeading: props.components.TableHeading,
18 | TableBody: props.components.TableBody,
19 | Loading: props.components.Loading,
20 | NoResults: props.components.NoResults,
21 | })),
22 | connect(
23 | (state, props) => ({
24 | dataLoading: dataLoadingSelector(state),
25 | visibleRows: visibleRowCountSelector(state),
26 | className: classNamesForComponentSelector(state, 'Table'),
27 | style: stylesForComponentSelector(state, 'Table'),
28 | })
29 | ),
30 | )(props => );
31 |
32 | export default ComposedContainerComponent;
33 |
--------------------------------------------------------------------------------
/src/plugins/local/components/TableBodyContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from '../../../utils/griddleConnect';
4 | import compose from 'recompose/compose';
5 | import mapProps from 'recompose/mapProps';
6 | import getContext from 'recompose/getContext';
7 |
8 | import { classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/localSelectors';
9 |
10 | const ComposedTableBodyContainer = OriginalComponent => compose(
11 | getContext({
12 | components: PropTypes.object,
13 | selectors: PropTypes.object,
14 | }),
15 | mapProps(props => ({
16 | Row: props.components.Row,
17 | visibleRowIdsSelector: props.selectors.visibleRowIdsSelector,
18 | ...props
19 | })),
20 | connect((state, props) => ({
21 | visibleRowIds: props.visibleRowIdsSelector(state),
22 | className: classNamesForComponentSelector(state, 'TableBody'),
23 | style: stylesForComponentSelector(state, 'TableBody'),
24 | })),
25 | // withHandlers({
26 | // Row: props => props.components.Row
27 | // })
28 | )(({ Row, visibleRowIds, style, className }) => (
29 |
35 | ));
36 |
37 | export default ComposedTableBodyContainer;
38 |
--------------------------------------------------------------------------------
/src/core/initialState.js:
--------------------------------------------------------------------------------
1 | const styleConfig = {
2 | icons: {
3 | TableHeadingCell: {
4 | sortDescendingIcon: '▼',
5 | sortAscendingIcon: '▲'
6 | },
7 | },
8 | classNames: {
9 | Cell: 'griddle-cell',
10 | Filter: 'griddle-filter',
11 | Loading: 'griddle-loadingResults',
12 | NextButton: 'griddle-next-button',
13 | NoResults: 'griddle-noResults',
14 | PageDropdown: 'griddle-page-select',
15 | Pagination: 'griddle-pagination',
16 | PreviousButton: 'griddle-previous-button',
17 | Row: 'griddle-row',
18 | RowDefinition: 'griddle-row-definition',
19 | Settings: 'griddle-settings',
20 | SettingsToggle: 'griddle-settings-toggle',
21 | Table: 'griddle-table',
22 | TableBody: 'griddle-table-body',
23 | TableHeading: 'griddle-table-heading',
24 | TableHeadingCell: 'griddle-table-heading-cell',
25 | TableHeadingCellAscending: 'griddle-heading-ascending',
26 | TableHeadingCellDescending: 'griddle-heading-descending',
27 | },
28 | styles: {
29 | }
30 | };
31 |
32 | export default {
33 | styleConfig,
34 |
35 | pageProperties: {
36 | currentPage: 1,
37 | pageSize: 10
38 | },
39 | enableSettings: true,
40 | textProperties: {
41 | filterPlaceholder: 'Filter',
42 | next: 'Next',
43 | previous: 'Previous',
44 | settingsToggle: 'Settings',
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/src/utils/__tests__/rowUtilsTests.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 |
3 | import { getRowProperties } from '../rowUtils';
4 |
5 | test('row utils gets object from react component', test => {
6 | const rowProperties = {
7 | props: {
8 | onHover: 'hi',
9 | onClick: 'hello',
10 | somethingElse: 'nothing',
11 | children: [
12 | { props: { id: 1, name: "one"}},
13 | { props: { id: 2, name: "two"}}
14 | ]
15 | }
16 | };
17 |
18 | const transformedRowProperties = getRowProperties(rowProperties);
19 | test.deepEqual(transformedRowProperties, {
20 | onHover: 'hi',
21 | onClick: 'hello',
22 | somethingElse: 'nothing',
23 | childColumnName: 'children'
24 | })
25 | });
26 |
27 | test('row utils uses provided childColumnName instead of default', test => {
28 | const rowProperties = {
29 | props: {
30 | onHover: 'hi',
31 | onClick: 'hello',
32 | somethingElse: 'nothing',
33 | childColumnName: 'somethingElse',
34 | children: [
35 | { props: { id: 1, name: "one"}},
36 | { props: { id: 2, name: "two"}}
37 | ]
38 | }
39 | };
40 |
41 | const transformedRowProperties = getRowProperties(rowProperties);
42 | test.deepEqual(transformedRowProperties, {
43 | onHover: 'hi',
44 | onClick: 'hello',
45 | somethingElse: 'nothing',
46 | childColumnName: 'somethingElse'
47 | })
48 |
49 | })
--------------------------------------------------------------------------------
/src/components/RowContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from '../utils/griddleConnect';
4 | import compose from 'recompose/compose';
5 | import mapProps from 'recompose/mapProps';
6 | import getContext from 'recompose/getContext';
7 |
8 | import {
9 | columnIdsSelector,
10 | rowDataSelector,
11 | rowPropertiesSelector,
12 | classNamesForComponentSelector,
13 | stylesForComponentSelector,
14 | } from '../selectors/dataSelectors';
15 | import { valueOrResult } from '../utils/valueUtils';
16 |
17 | const ComposedRowContainer = OriginalComponent => compose(
18 | getContext({
19 | components: PropTypes.object,
20 | }),
21 | connect((state, props) => ({
22 | columnIds: columnIdsSelector(state),
23 | rowProperties: rowPropertiesSelector(state),
24 | rowData: rowDataSelector(state, props),
25 | className: classNamesForComponentSelector(state, 'Row'),
26 | style: stylesForComponentSelector(state, 'Row'),
27 | })),
28 | mapProps(props => {
29 | const { components, rowProperties, className, ...otherProps } = props;
30 | return {
31 | Cell: components.Cell,
32 | className: valueOrResult(rowProperties.cssClassName, props) || props.className,
33 | ...otherProps,
34 | };
35 | }),
36 | )(props => (
37 |
40 | ));
41 |
42 | export default ComposedRowContainer;
43 |
--------------------------------------------------------------------------------
/src/actions/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | GRIDDLE_NEXT_PAGE,
3 | GRIDDLE_PREVIOUS_PAGE,
4 | GRIDDLE_SET_PAGE,
5 | GRIDDLE_SET_SORT,
6 | GRIDDLE_SET_FILTER,
7 | GRIDDLE_TOGGLE_SETTINGS,
8 | GRIDDLE_TOGGLE_COLUMN,
9 | GRIDDLE_SET_PAGE_SIZE,
10 | GRIDDLE_UPDATE_STATE,
11 | } from '../constants';
12 |
13 | export function getNext() {
14 | return {
15 | type: GRIDDLE_NEXT_PAGE
16 | }
17 | }
18 |
19 | export function getPrevious() {
20 | return {
21 | type: GRIDDLE_PREVIOUS_PAGE
22 | }
23 | }
24 |
25 | export function setPage(pageNumber) {
26 | return {
27 | type: GRIDDLE_SET_PAGE,
28 | pageNumber
29 | };
30 | }
31 |
32 | export function setFilter(filter) {
33 | return {
34 | type: GRIDDLE_SET_FILTER,
35 | filter
36 | }
37 | }
38 |
39 | export function setSortColumn(sortProperties) {
40 | return {
41 | type: GRIDDLE_SET_SORT,
42 | sortProperties
43 | }
44 | }
45 |
46 | export function toggleSettings() {
47 | return {
48 | type: GRIDDLE_TOGGLE_SETTINGS
49 | }
50 | }
51 |
52 | export function toggleColumn(columnId) {
53 | return {
54 | type: GRIDDLE_TOGGLE_COLUMN,
55 | columnId
56 | }
57 | }
58 |
59 | export function setPageSize(pageSize) {
60 | return {
61 | type: GRIDDLE_SET_PAGE_SIZE,
62 | pageSize
63 | }
64 | }
65 |
66 | export function updateState(newState) {
67 | return {
68 | type: GRIDDLE_UPDATE_STATE,
69 | newState
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/plugins/local/components/TableHeadingContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from '../../../utils/griddleConnect';
4 | import compose from 'recompose/compose';
5 | import mapProps from 'recompose/mapProps';
6 | import getContext from 'recompose/getContext';
7 | import { columnTitlesSelector, columnIdsSelector, classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/localSelectors';
8 |
9 | const ComposedContainerComponent = OriginalComponent => compose(
10 | getContext({
11 | components: PropTypes.object
12 | }),
13 | connect((state) => ({
14 | columnTitles: columnTitlesSelector(state),
15 | columnIds: columnIdsSelector(state),
16 | className: classNamesForComponentSelector(state, 'TableHeading'),
17 | style: stylesForComponentSelector(state, 'TableHeading'),
18 | })),
19 | mapProps(props => ({
20 | TableHeadingCell: props.components.TableHeadingCell,
21 | ...props
22 | }))
23 | // withHandlers({
24 | // TableHeadingCell: props => props.components.TableHeadingCell
25 | // })
26 | )(({TableHeadingCell, columnTitles, columnIds, className, style }) => (
27 |
34 | ));
35 |
36 | export default ComposedContainerComponent;
37 |
--------------------------------------------------------------------------------
/src/components/__tests__/NextButtonTest.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import test from 'ava';
3 | import { shallow } from 'enzyme';
4 |
5 | import NextButton from '../NextButton';
6 |
7 | test('renders when hasNext is true', (t) => {
8 | const wrapper = shallow();
9 | t.true(wrapper.matchesElement());
10 | });
11 |
12 | test('null when hasNext is false', (t) => {
13 | const wrapper = shallow();
14 | t.true(wrapper.matchesElement(null));
15 | });
16 |
17 | test('renders with style', (t) => {
18 | const style = { backgroundColor: '#EDEDED' };
19 | const wrapper = shallow();
20 |
21 | t.true(wrapper.matchesElement());
22 | });
23 |
24 | test('renders with className', (t) => {
25 | const wrapper = shallow();
26 |
27 | t.true(wrapper.matchesElement());
28 | });
29 |
30 | test('renders with appropriate text', (t) => {
31 | const wrapper = shallow();
32 | t.true(wrapper.matchesElement());
33 | });
34 |
35 | test('handles click', (t) => {
36 | let clicked = false;
37 | const onClick = () => { clicked = true; };
38 |
39 | const wrapper = shallow();
40 | wrapper.simulate('click');
41 |
42 | t.true(clicked);
43 | });
44 |
--------------------------------------------------------------------------------
/src/components/__tests__/CellTest.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import test from 'ava';
3 | import { shallow } from 'enzyme';
4 |
5 | import Cell from '../Cell';
6 |
7 | test('renders', (t) => {
8 | const wrapper = shallow( | );
9 | t.true(wrapper.matchesElement( | ));
10 | });
11 |
12 | test('renders with correct value', (t) => {
13 | const wrapper = shallow( | );
14 | t.true(wrapper.matchesElement(one two three | ));
15 | });
16 |
17 | test('onClick works', (t) => {
18 | let clicked = false;
19 |
20 | const onClick = () => {
21 | clicked = true;
22 | };
23 | const wrapper = shallow( | );
24 | wrapper.simulate('click');
25 |
26 | t.true(clicked);
27 | });
28 |
29 | test('onMouseEnter works', (t) => {
30 | let over = false;
31 |
32 | const onMouseEnter = () => {
33 | over = true;
34 | };
35 | const wrapper = shallow( | );
36 | wrapper.simulate('mouseEnter');
37 |
38 | t.true(over);
39 | });
40 |
41 | test('onMouseLeave works', (t) => {
42 | let leave = false;
43 |
44 | const onMouseLeave = () => (leave = true);
45 | const wrapper = shallow( | );
46 | wrapper.simulate('mouseLeave');
47 |
48 | t.true(leave);
49 | });
50 |
51 | test('class name gets applied', (t) => {
52 | const wrapper = shallow( | );
53 | t.true(wrapper.matchesElement( | ));
54 | });
55 |
--------------------------------------------------------------------------------
/src/components/__tests__/SettingsTest.js:
--------------------------------------------------------------------------------
1 | import 'jsdom-global/register';
2 | import React from 'react';
3 | import test from 'ava';
4 | import { shallow, mount } from 'enzyme';
5 |
6 | import Settings from '../Settings';
7 |
8 | test('renders with style', (t) => {
9 | const style = { backgroundColor: '#EDEDED' };
10 | const wrapper = shallow();
11 |
12 | t.true(wrapper.matchesElement());
13 | });
14 |
15 | test('renders with className', (t) => {
16 | const wrapper = shallow();
17 |
18 | t.true(wrapper.matchesElement());
19 | });
20 |
21 | test('renders each settings component', (t) => {
22 | const component1 = () => One
;
23 | const component2 = () => Two
;
24 |
25 | const settingsComponents = [component1, component2];
26 |
27 | const wrapper = mount();
28 | const one = wrapper.find('.one');
29 | const two = wrapper.find('.two');
30 |
31 | t.true(one.matchesElement(One
));
32 | t.true(two.matchesElement(Two
));
33 | });
34 |
35 | test('ignores missing components', (t) => {
36 | const settingsComponents = [null, undefined];
37 | const wrapper = shallow();
38 |
39 | t.true(wrapper.matchesElement());
40 | });
41 |
--------------------------------------------------------------------------------
/src/components/__tests__/PaginationTest.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import test from 'ava';
3 | import { shallow } from 'enzyme';
4 |
5 | import Pagination from '../Pagination';
6 |
7 | test('renders', (t) => {
8 | const wrapper = shallow();
9 |
10 | t.true(wrapper.matchesElement());
11 | });
12 |
13 | test('renders with style', (t) => {
14 | const style = { backgroundColor: "#EDEDED" };
15 | const wrapper = shallow();
16 |
17 | t.true(wrapper.matchesElement());
18 | });
19 |
20 | test('renders with className', (t) => {
21 | const wrapper = shallow();
22 |
23 | t.true(wrapper.matchesElement());
24 | });
25 |
26 | test('renders children', (t) => {
27 | const next = () => ;
28 | const previous = () => ;
29 | const pageDropdown = () => Dropdown
;
30 |
31 | const wrapper = shallow();
36 |
37 | const previousRendered = wrapper.childAt(0);
38 | t.is(previousRendered.html(), '');
39 |
40 | const pageDropdownRendered = wrapper.childAt(1);
41 | t.is(pageDropdownRendered.html(), 'Dropdown
');
42 |
43 | const nextRendered = wrapper.childAt(2);
44 | t.is(nextRendered.html(), '');
45 | });
46 |
47 |
--------------------------------------------------------------------------------
/src/utils/__tests__/dataUtilsTests.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 |
3 | import Immutable from 'immutable';
4 |
5 | import {
6 | hasData,
7 | transformData,
8 | } from '../dataUtils';
9 |
10 | const collection = Immutable.fromJS([
11 | { name: 'one' },
12 | { name: 'two' },
13 | { name: 'three' }
14 | ]);
15 |
16 | test('hasData is false when data does not exist', (assert) => {
17 | const res = hasData({});
18 | assert.is(res, false);
19 | });
20 |
21 | [undefined, null].map(data =>
22 | test(`hasData is false when data is ${data}`, (assert) => {
23 | const res = hasData({ data });
24 | assert.is(res, false);
25 | })
26 | );
27 |
28 | test('hasData is false when data is empty', (assert) => {
29 | const res = hasData({ data: [] });
30 | assert.is(res, false);
31 | });
32 |
33 | test('hasData is false when data is not empty', (assert) => {
34 | const res = hasData({ data: [{}] });
35 | assert.is(res, true);
36 | });
37 |
38 | test('transforms data', test => {
39 | const data = [
40 | { first: 'Luke', last: 'Skywalker' },
41 | { first: 'Darth', last: 'Vader' }
42 | ];
43 |
44 | const transformedData = transformData(data, {});
45 |
46 | test.deepEqual(Object.keys(transformedData), ['data', 'lookup']);
47 |
48 | test.deepEqual(transformedData.data.toJSON(), [
49 | { first: 'Luke', last: 'Skywalker', griddleKey: 0 },
50 | { first: 'Darth', last: 'Vader', griddleKey: 1 }
51 | ]);
52 |
53 | test.deepEqual(transformedData.lookup.toJSON(), { 0: 0, 1: 1 });
54 | });
55 |
--------------------------------------------------------------------------------
/src/components/__tests__/PreviousButtonTest.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import test from 'ava';
3 | import { shallow } from 'enzyme';
4 |
5 | import PreviousButton from '../PreviousButton';
6 |
7 | test('renders when hasPrevious is true', (t) => {
8 | const wrapper = shallow();
9 | t.true(wrapper.matchesElement());
10 | });
11 |
12 | test('null when hasPrevious is false', (t) => {
13 | const wrapper = shallow();
14 | t.true(wrapper.matchesElement(null));
15 | });
16 |
17 | test('renders with style', (t) => {
18 | const style = { backgroundColor: '#EDEDED' };
19 | const wrapper = shallow();
20 |
21 | t.true(wrapper.matchesElement());
22 | });
23 |
24 | test('renders with className', (t) => {
25 | const wrapper = shallow();
26 |
27 | t.true(wrapper.matchesElement());
28 | });
29 |
30 | test('renders with appropriate text', (t) => {
31 | const wrapper = shallow();
32 | t.true(wrapper.matchesElement());
33 | });
34 |
35 | test('handles click', (t) => {
36 | let clicked = false;
37 | const onClick = () => { clicked = true; };
38 |
39 | const wrapper = shallow();
40 | wrapper.simulate('click');
41 |
42 | t.true(clicked);
43 | });
44 |
45 |
--------------------------------------------------------------------------------
/src/components/__tests__/TableTest.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import test from 'ava';
3 | import { shallow } from 'enzyme';
4 |
5 | import Table from '../Table';
6 |
7 | test('renders', (t) => {
8 | const wrapper = shallow();
9 |
10 | t.true(wrapper.matchesElement());
11 | });
12 |
13 | test('renders with style', (t) => {
14 | const style = { backgroundColor: '#EDEDED' };
15 | const wrapper = shallow();
16 |
17 | t.true(wrapper.matchesElement());
18 | });
19 |
20 | test('renders with className', (t) => {
21 | const wrapper = shallow();
22 |
23 | t.true(wrapper.matchesElement());
24 | });
25 |
26 | test('renders TableHeading', (t) => {
27 | const TableHeading = () => ;
28 | const wrapper = shallow();
29 |
30 | t.true(wrapper.matchesElement());
31 | });
32 |
33 | test('renders TableBody', (t) => {
34 | const TableBody = () => ;
35 | const wrapper = shallow();
36 |
37 | t.true(wrapper.matchesElement());
38 | });
39 |
40 | test('renders NoResults when noRows', (t) => {
41 | const NoResults = () => ;
42 | const wrapper = shallow();
43 |
44 | t.true(wrapper.matchesElement());
45 | });
46 |
--------------------------------------------------------------------------------
/src/components/__tests__/TableHeadingTest.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import test from 'ava';
3 | import { shallow } from 'enzyme';
4 |
5 | import TableHeading from '../TableHeading';
6 |
7 | test('renders', (t) => {
8 | const wrapper = shallow();
9 |
10 | t.true(wrapper.matchesElement(
));
11 | });
12 |
13 | test('renders with style', (t) => {
14 | const style = { backgroundColor: '#EDEDED' };
15 | const wrapper = shallow();
16 |
17 | t.true(wrapper.matchesElement(
));
18 | });
19 |
20 | test('renders with className', (t) => {
21 | const wrapper = shallow();
22 |
23 | t.true(wrapper.matchesElement(
));
24 | });
25 |
26 | test('renders with TableHeadingCell for columnTitles and columnIds', (t) => {
27 | const columnTitles = ['one', 'two', 'three'];
28 | const columnIds = [1, 2, 3];
29 | const TableHeadingCell = ({key, title, columnId}) => {title} | ;
30 | const wrapper = shallow();
31 |
32 | t.true(wrapper.matchesElement(
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | ));
41 | });
42 |
--------------------------------------------------------------------------------
/.storybook/webpack-build.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const include = path.resolve(__dirname, '../');
3 |
4 | // you can use this file to add your custom webpack plugins, loaders and anything you like.
5 | // This is just the basic way to add addional webpack configurations.
6 | // For more information refer the docs: https://goo.gl/qPbSyX
7 |
8 | // IMPORTANT
9 | // When you add this file, we won't add the default configurations which is similar
10 | // to "React Create App". This only has babel loader to load JavaScript.
11 |
12 | module.exports = {
13 | devtool: 'source-map',
14 | entry: './stories/index.tsx',
15 | output: {
16 | path: path.join(__dirname, '/dist/examples/'),
17 | filename: 'storybook.js'
18 | },
19 | resolve: {
20 | extensions: ['.webpack.js', '.web.js', '.ts', '.tsx', '.js']
21 | },
22 | module: {
23 | rules: [
24 | {
25 | test: /\.(ts|tsx)$/,
26 | use: [
27 | {
28 | loader: 'babel-loader'
29 | },
30 | {
31 | loader: 'awesome-typescript-loader'
32 | },
33 | {
34 | loader: 'react-docgen-typescript-loader'
35 | }
36 | ],
37 | exclude: ['/node_modules/']
38 | },
39 | {
40 | test: /\.(js|jsx)$/,
41 | use: [
42 | {
43 | loader: 'babel-loader',
44 | options: {
45 | presets: ['@babel/preset-env', '@babel/preset-react']
46 | }
47 | }
48 | ],
49 | exclude: ['/node_modules/']
50 | }
51 | ]
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/src/plugins/local/components/RowContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from '../../../utils/griddleConnect';
4 | import compose from 'recompose/compose';
5 | import mapProps from 'recompose/mapProps';
6 | import getContext from 'recompose/getContext';
7 |
8 | import {
9 | columnIdsSelector,
10 | rowDataSelector,
11 | rowPropertiesSelector,
12 | classNamesForComponentSelector,
13 | stylesForComponentSelector,
14 | } from '../selectors/localSelectors';
15 | import { valueOrResult } from '../../../utils/valueUtils';
16 |
17 | const ComposedRowContainer = OriginalComponent => compose(
18 | getContext({
19 | components: PropTypes.object
20 | }),
21 | connect((state, props) => ({
22 | columnIds: columnIdsSelector(state),
23 | rowProperties: rowPropertiesSelector(state),
24 | rowData: rowDataSelector(state, props),
25 | className: classNamesForComponentSelector(state, 'Row'),
26 | style: stylesForComponentSelector(state, 'Row'),
27 | })),
28 | mapProps((props) => {
29 | const { components, rowProperties, className, ...otherProps } = props;
30 | return {
31 | Cell: components.Cell,
32 | className: valueOrResult(rowProperties.cssClassName, props) || props.className,
33 | ...otherProps,
34 | };
35 | }),
36 | )(({Cell, columnIds, griddleKey, style, className }) => (
37 |
44 | ));
45 |
46 | export default ComposedRowContainer;
47 |
--------------------------------------------------------------------------------
/src/components/__tests__/TableHeadingCellTest.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import test from 'ava';
3 | import { shallow } from 'enzyme';
4 |
5 | import TableHeadingCell from '../TableHeadingCell';
6 |
7 | test('renders with style', (t) => {
8 | const style = { backgroundColor: '#EDEDED' };
9 | const wrapper = shallow();
10 |
11 | t.true(wrapper.matchesElement( | ));
12 | });
13 |
14 | test('renders with className', (t) => {
15 | const wrapper = shallow();
16 |
17 | t.true(wrapper.matchesElement( | ));
18 | });
19 |
20 | test('renders title', (t) => {
21 | const wrapper = shallow();
22 |
23 | t.true(wrapper.matchesElement(Hi!!1! | ));
24 | });
25 |
26 | test('fires onClick', (t) => {
27 | let clicked = false;
28 | const onClick = () => { clicked = true; };
29 | const wrapper = shallow();
30 |
31 | wrapper.simulate('click');
32 | t.true(clicked);
33 | });
34 |
35 | test('fires onMouseEnter', (t) => {
36 | let hovered = false;
37 | const onMouseEnter = () => { hovered = true; };
38 | const wrapper = shallow();
39 |
40 | wrapper.simulate('mouseEnter');
41 | t.true(hovered);
42 | });
43 |
44 | test('fires onMouseLeave', (t) => {
45 | let left = false;
46 | const onMouseLeave = () => { left = true; };
47 | const wrapper = shallow();
48 |
49 | wrapper.simulate('mouseLeave');
50 | t.true(left);
51 | });
52 |
--------------------------------------------------------------------------------
/src/components/SettingsContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from '../utils/griddleConnect';
4 | import compose from 'recompose/compose';
5 | import mapProps from 'recompose/mapProps';
6 | import getContext from 'recompose/getContext';
7 |
8 | import { classNamesForComponentSelector, stylesForComponentSelector } from '../selectors/dataSelectors';
9 |
10 | function getSettingsComponentsArrayFromObject(settingsObject, settingsComponents) {
11 | //TODO: determine if we need to make this faster
12 | return settingsObject ? Object.keys(settingsObject)
13 | .sort((a, b) => {
14 | var oa = settingsObject[a], ob = settingsObject[b];
15 | return ((oa && oa.order) || 0) - ((ob && ob.order) || 0);
16 | })
17 | .map(key => settingsObject[key] && (settingsObject[key].component || (settingsComponents && settingsComponents[key]))) : null;
18 | }
19 |
20 | const EnhancedSettings = OriginalComponent => compose(
21 | getContext({
22 | components: PropTypes.object,
23 | settingsComponentObjects: PropTypes.object
24 | }),
25 | connect(
26 | (state, props) => ({
27 | className: classNamesForComponentSelector(state, 'Settings'),
28 | style: stylesForComponentSelector(state, 'Settings'),
29 | })
30 | ),
31 | mapProps(props => {
32 | const { components, settingsComponentObjects, ...otherProps } = props;
33 | return {
34 | settingsComponents: getSettingsComponentsArrayFromObject(settingsComponentObjects, components.SettingsComponents),
35 | ...otherProps,
36 | };
37 | })
38 | )(props => (
39 |
40 | ));
41 |
42 | export default EnhancedSettings;
43 |
--------------------------------------------------------------------------------
/src/plugins/position/components/SpacerRow.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from '../../../utils/griddleConnect';
4 | import compose from 'recompose/compose';
5 | import mapProps from 'recompose/mapProps';
6 | import getContext from 'recompose/getContext';
7 | import withHandlers from 'recompose/withHandlers';
8 |
9 | const spacerRow = compose(
10 | getContext({
11 | selectors: PropTypes.object,
12 | }),
13 | connect((state, props) => {
14 | const { topSpacerSelector, bottomSpacerSelector } = props.selectors;
15 | const { placement } = props;
16 |
17 | return {
18 | spacerHeight: placement === 'top' ? topSpacerSelector(state, props) : bottomSpacerSelector(state, props),
19 | };
20 | }),
21 | mapProps(props => ({
22 | placement: props.placement,
23 | spacerHeight: props.spacerHeight,
24 | }))
25 | )(class extends Component {
26 | static propTypes = {
27 | placement: PropTypes.string,
28 | spacerHeight: PropTypes.number,
29 | }
30 | static defaultProps = {
31 | placement: 'top'
32 | }
33 |
34 | // shouldComponentUpdate(nextProps) {
35 | // const { currentPosition: oldPosition, placement: oldPlacement } = this.props;
36 | // const { currentPosition, placement } = nextProps;
37 | //
38 | // return oldPosition !== currentPosition || oldPlacement !== placement;
39 | // }
40 |
41 | render() {
42 | const { placement, spacerHeight } = this.props;
43 | let spacerRowStyle = {
44 | height: `${spacerHeight}px`,
45 | };
46 |
47 | return (
48 |
49 | );
50 | }
51 | });
52 |
53 | export default spacerRow;
54 |
--------------------------------------------------------------------------------
/src/components/__tests__/SettingsWrapperTest.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import test from 'ava';
3 | import { shallow } from 'enzyme';
4 |
5 | import SettingsWrapper from '../SettingsWrapper';
6 |
7 | test('renders if isEnabled', (t) => {
8 | const wrapper = shallow();
9 |
10 | t.true(wrapper.matchesElement());
11 | });
12 |
13 | test('renders with settings toggle when provided', (t) => {
14 | const settingsToggle = () => Toggle
;
15 | const wrapper = shallow();
16 |
17 | t.is(wrapper.html(), '');
18 | });
19 |
20 | test('renders with style', (t) => {
21 | const style = { backgroundColor: '#EDEDED' };
22 | const wrapper = shallow();
23 |
24 | t.true(wrapper.matchesElement());
25 | });
26 |
27 | test('renders with className', (t) => {
28 | const wrapper = shallow();
29 |
30 | t.true(wrapper.matchesElement());
31 | });
32 |
33 | test('renders with settings if visible and settings component is provided', (t) => {
34 | const settings = () => Settings
;
35 | const wrapper = shallow();
36 |
37 | t.is(wrapper.html(), '');
38 | });
39 |
40 | test('renders without settings if isVisible is false and settings component is provided', (t) => {
41 | const settings = () => Settings
;
42 | const wrapper = shallow();
43 |
44 | t.is(wrapper.html(), '');
45 | });
46 |
--------------------------------------------------------------------------------
/src/utils/listenerUtils.js:
--------------------------------------------------------------------------------
1 | export const StoreListener = class StoreListener {
2 | constructor(store) {
3 | this.store = store;
4 | this.unsubscribers = {};
5 | }
6 |
7 | removeListener = (name) => {
8 | if (this.unsubscribers.hasOwnProperty(name)) {
9 | this.unsubscribers[name]();
10 | delete this.unsubscribers[name];
11 | return true;
12 | } else {
13 | return false;
14 | }
15 | }
16 |
17 | // Adds a listener to the store.
18 | // Will attempt to remove an existing listener if the name
19 | // matches that of an existing listener.
20 | // If no name is provided this is an anonymous lister, it
21 | // is not registered in the list of unsubscribe functions,
22 | // returns the unsubscribe function so it can still be handled
23 | // manually if desired.
24 | addListener = (listener, name, otherArgs) => {
25 | // attempt to unsubscribe an existing listener if the new
26 | // listener name matches
27 | // if no name is provided, do nothing
28 | name && this.removeListener(name);
29 | const unsubscribe = (() => {
30 | let oldState;
31 | return this.store.subscribe(() => {
32 | const newState = this.store.getState();
33 | listener(oldState, newState, {...otherArgs});
34 | oldState = newState;
35 | });
36 | })();
37 | // if name was provided, add the unsubscribe
38 | // otherwise this is an "anonymous" listener
39 | name && (this.unsubscribers[name] = unsubscribe);
40 | return unsubscribe;
41 | }
42 |
43 | hasListener = (name) => {
44 | return this.unsubscribers.hasOwnProperty(name);
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/src/utils/__tests__/columnUtilsTests.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 |
3 | import { getColumnProperties } from '../columnUtils';
4 |
5 | test('get column properties works with array', test => {
6 | const rowProperties = {
7 | props: {
8 | children: [
9 | { props: { id: 1, name: "one"}},
10 | { props: { id: 2, name: "two", order: 5 }}
11 | ]
12 | }
13 | };
14 |
15 | const columnProperties = getColumnProperties(rowProperties);
16 |
17 | test.deepEqual(columnProperties, {
18 | 1: { id: 1, name: "one", order: 1000 },
19 | 2: { id: 2, name: "two", order: 5 },
20 | });
21 | });
22 |
23 | test('get column properties works with single column property object', test => {
24 | const rowProperties = {
25 | props: {
26 | children: { props: { id: 1, name: 'one' }},
27 | }
28 | };
29 |
30 | const columnProperties = getColumnProperties(rowProperties);
31 |
32 | test.deepEqual(columnProperties, {
33 | 1: { id: 1, name: 'one', order: 1000 },
34 | });
35 | });
36 |
37 | test('get column properties returns all columns when no property columns specified', test => {
38 | const rowProperties = {
39 | props: {}
40 | };
41 |
42 | const allColumns = ['one', 'two', 'three'];
43 |
44 | const columnProperties = getColumnProperties(rowProperties, allColumns);
45 |
46 | test.deepEqual(columnProperties, {
47 | one: { id: 'one', order: 1000 },
48 | two: { id: 'two', order: 1001 },
49 | three: { id: 'three', order: 1002 }
50 | });
51 | });
52 |
53 | test('get column properties ignores falsy values in array', test => {
54 | const rowProperties = {
55 | props: {
56 | children: [
57 | { props: { id: 1, name: "one"}},
58 | 0,
59 | undefined,
60 | null,
61 | false,
62 | ]
63 | }
64 | };
65 |
66 | const columnProperties = getColumnProperties(rowProperties);
67 |
68 | test.is(Object.keys(columnProperties).length, 1);
69 | });
70 |
--------------------------------------------------------------------------------
/src/components/__tests__/PageDropdownTest.js:
--------------------------------------------------------------------------------
1 | import 'jsdom-global/register';
2 | import React from 'react';
3 | import test from 'ava';
4 | import { shallow, mount } from 'enzyme';
5 |
6 | import PageDropdown from '../PageDropdown';
7 |
8 | // TODO: This whole test is pretty bad. I couldn't get the matches element working with option for some reason
9 |
10 | test('renders with style', (t) => {
11 | const style = { backgroundColor: '#EDEDED' };
12 | const wrapper = shallow();
13 | const node = wrapper.getElements()[0];
14 | t.is(node.type, 'select');
15 | t.is(node.props.style, style);
16 | });
17 |
18 | test('renders with className', (t) => {
19 | const wrapper = shallow();
20 | const node = wrapper.getElements()[0];
21 | t.is(node.type, 'select');
22 | t.is(node.props.className, 'className');
23 | });
24 |
25 | test('renders with option element at page 0 if no pages provided', (t) => {
26 | const wrapper = shallow();
27 | const option = wrapper.childAt(0);
28 |
29 | t.is(option.html(), '');
30 | });
31 |
32 | test('renders with as many option elements as max pages', (t) => {
33 | const wrapper = shallow();
34 |
35 | t.is(wrapper.children().length, 10);
36 | });
37 |
38 | test('renders at selected page', (t) => {
39 | // using mount because attempting to get selectedIndex later on
40 | const wrapper = mount();
41 |
42 | const select = wrapper.find('select');
43 | t.is(select.props().value, 3);
44 | });
45 |
46 | test('fires set page event', (t) => {
47 | let changed = false;
48 | const onChange = () => {
49 | changed = true;
50 | };
51 | const wrapper = mount(
52 |
53 | );
54 | wrapper.simulate('change', { target: { value: 3 } });
55 | t.true(changed);
56 | });
57 |
--------------------------------------------------------------------------------
/src/utils/columnUtils.js:
--------------------------------------------------------------------------------
1 | const offset = 1000;
2 |
3 | /** Gets a column properties object from an array of columnNames
4 | * @param {Array} columns - array of column names
5 | */
6 | function getColumnPropertiesFromColumnArray(columnProperties, columns) {
7 | return columns.reduce((previous, current, i) => {
8 | previous[current] = { id: current, order: offset + i };
9 | return previous;
10 | },
11 | columnProperties);
12 | }
13 |
14 | /** Gets the column properties object from a react component (rowProperties) that contains child component(s) for columnProperties.
15 | * If no properties are found, it will work return a column properties object based on the all columns array
16 | * @param {Object} rowProperties - An React component that contains the rowProperties and child columnProperties components
17 | * @param {Array optional} allColumns - An optional array of colummn names. This will be used to generate the columnProperties when they are not defined in rowProperties
18 | */
19 | export function getColumnProperties(rowProperties, allColumns=[]) {
20 | const children = rowProperties && rowProperties.props && rowProperties.props.children;
21 | const columnProperties = {};
22 |
23 | // Working against an array of columnProperties
24 | if (Array.isArray(children)) {
25 | // build one object that contains all of the column properties keyed by id
26 | children.reduce((previous, current, i) => {
27 | if (current) {
28 | previous[current.props.id] = {order: offset + i, ...current.props};
29 | }
30 | return previous;
31 | }, columnProperties);
32 |
33 | // Working against a lone, columnProperties object
34 | } else if (children && children.props) {
35 | columnProperties[children.props.id] = { order: offset, ...children.props };
36 | }
37 |
38 | if(Object.keys(columnProperties).length === 0 && allColumns) {
39 | getColumnPropertiesFromColumnArray(columnProperties, allColumns);
40 | }
41 |
42 | return columnProperties;
43 | }
44 |
--------------------------------------------------------------------------------
/src/utils/sortUtils.js:
--------------------------------------------------------------------------------
1 | /* Sorts the given data by the specified column
2 | * @parameter {array