Skip to content

Commit

Permalink
[WIP] EuiDataGrid (#2165)
Browse files Browse the repository at this point in the history
* initial datagrid

* protect against re-rendering content on column width change

* Added unit initial tests for EuiDataGrid

* [EuiDataGrid] Base layer Sass (#2171)

* Initial css fixes

* works in IE, addresses feedback

* remove user select

* Adds basic aria roles and grid navigation (#2187)

* Adds basic aria roles and grid navigation

Co-Authored-By: Chandler Prall <[email protected]>

* [Data Grid] Grid style options (#2176)

Adds basic styling to the data grid.

* Add pagination to EuiDataGrid (#2188)

* Added pagination to to EuiDataGrid

* Move EuiDataGrid row rendering to a sub-component to clean up state management

* EuiDataGrid pagination unit tests

* fix data grid pagination

* revert colors

* EuiDataGrid column visibility & ordering (#2207)

* Show/hide and re-order datagrid columns

* Column visability & ordering tests

* column styling

* column sizing and bars

* blergh

* tests

* feedback

* Fix linting

* Adds more complex focus control for DataGrid (#2222)

* [Data Grid] - Styling built into data grid, full screen mode (#2230)

Styling built into data grid, full screen mode

* EuiDataGrid add column sorting (#2278)

* API interface for providing column sort order, callback to allow external data sorting

* EuiDataGrid renders content into memory, sorts on it

* Added tests for EuiDataGrid sorting

* Added aria-sort value to a singly-sorted column header

* small cleanup

* add tests back in, though they are still broken

* Clean up some keyboard navigation issues

* Fix column sorting & update snapshots

* EuiDataGrid hooks cleanup (#2331)

* Refactored EuiDataGrid's hooks

* Fix datagrid to react to gridStyle changes

* [EuiDataGrid] Automatic column schema detection (#2351)

* Automatically detect data schema for in-memory datagrid

* Merge in described schema for field formatting

* Better column type detection

* Tests for euidatagrid schema / column type

* refactor datagrid schema code, add datetime type detection

* some comments

* Allow extra type detectors for EuiDataGrid

* cleanup of docs and type formatting

* Fix datagrid unit test

* Update currency detector

* Allow EuiDataGrid's inMemory prop to be {true}

* Added ability to provide extra props for the containing cell div

* Added test for cell props

* [EuiDataGrid] InMemory Performance (#2374)

* Automatically detect data schema for in-memory datagrid

* Merge in described schema for field formatting

* Better column type detection

* Tests for euidatagrid schema / column type

* refactor datagrid schema code, add datetime type detection

* some comments

* Allow extra type detectors for EuiDataGrid

* cleanup of docs and type formatting

* Fix datagrid unit test

* Update currency detector

* Allow EuiDataGrid's inMemory prop to be {true}

* Added ability to provide extra props for the containing cell div

* Added test for cell props

* Performance cleanups

* Clean up datagrid doc's inMemory selection

* Merged in feature branch

* EuiDataGrid in-memory options

* Performance refactor for in-memory values

* added a comment

* Fix sorting on in-memory and schema datagrid docs

* [EuiDataGrid] Feature/euidatagrid menu ux (#2392)

Moved the sorting mechanism to the top toolbar.

* Export useRenderToText to top-level package (#2412)

* [DATA GRID] Expand cells (#2418)

Data grid cells now can expand and can render individually based upon their schema.

* [EuiDataGrid] use schema information when sorting (#2419)

* cell expansion working mostly

* fix double import

* add search to field selector

* euitext

* cell epansion is now optional through a config

* keydown event for cells

* remove tabbables

* Clean up some code & tests

* Remove unused line of code

* Center popover against cell

* Update euidatagridcell popover placement, trigger, dom structure, and auto focusing

* Restore focus to grid cell when popover was in response to mouse click

* Allow grid column selection to be searchable

* Refactor expansion popover formatting, allow custom ones

* schema-based sort comparators

* reverse boolean sort to be true-false

* adds json schema sorting, fixes issue with popover

* Weaken the currency type detector when values have a period in their first few characters, and fix test

* Move column order and visibility to be managed externally (#2422)

* fix tests

* [EuiDataGrid] Custom column headers (#2421)

* Allow custom ReactNode for column header display

* Allow navigation into grid headers if any are interactive

* Properly wrap cell focus and use [enter], [f2] to interact

* Corrected header cell focus-state on blurring, [escape]. and single interactives

* Corrected header cell focus-state on blurring, [escape]. and single interactives

* When datagrid header is interactive, default its tabstop to the first header cell

* EuiDataGridHeaderCell warns about multiple interactive elements

* fix focus, example and screenreader stuffs, looks like tests pass

* simplifying screen reader read out

* [DATA GRID] EuiGridToolBar toolbar is now configurable through props (#2443)

* EuiGridToolBar toolbar is now configurable through props

* better tests

* small test typp

* Update src/components/datagrid/data_grid_types.ts

Co-Authored-By: Greg Thompson <[email protected]>

* feedback

* [EuiDataGrid] Docs and autodocs (#2449)

* Render out EuiDataGrid proptypes

* Add pagination props to docs

* Fill out all datagrid autodoc sections

* remove debugger statement

* Update src/components/datagrid/data_grid_types.ts

Co-Authored-By: Greg Thompson <[email protected]>

* words

* docs start

* datatype renamed to schema, update docs

* docs, fix typo for fullscreen buton

* core concepts

* better in memory explanation

* custom schema example

* provide a nice, documented snippet

* typos

* don't show pagination when only one page

* clean up styling, better docs for formatters

* more docs cleanup

* IE fix

* IE fix again

* small cleanup of docs

* describe how to disable expansion popovers

* dark mode tweaks

* Fix custom datatype sorting

* Update src-docs/src/views/datagrid/datagrid_example.js

Co-Authored-By: Michail Yasonik <[email protected]>

* Update src-docs/src/views/datagrid/datagrid_example.js

Co-Authored-By: Michail Yasonik <[email protected]>

* Update src-docs/src/views/datagrid/datagrid_example.js

Co-Authored-By: Michail Yasonik <[email protected]>

* Update src-docs/src/views/datagrid/datagrid_example.js

Co-Authored-By: Michail Yasonik <[email protected]>

* Update src-docs/src/views/datagrid/datagrid_example.js

Co-Authored-By: Michail Yasonik <[email protected]>

* Update src-docs/src/views/datagrid/datagrid_example.js

Co-Authored-By: Michail Yasonik <[email protected]>

* Update src-docs/src/views/datagrid/datagrid_example.js

Co-Authored-By: Michail Yasonik <[email protected]>

* Update src-docs/src/views/datagrid/datagrid_example.js

Co-Authored-By: Michail Yasonik <[email protected]>

* PR feedback

* typo

* feedback to break up docs

* better cross linking and summary

* fix custom schema display

* Update src-docs/src/views/datagrid/datagrid_memory_example.js

Co-Authored-By: Greg Thompson <[email protected]>

* Update src-docs/src/views/datagrid/datagrid_memory_example.js

Co-Authored-By: Greg Thompson <[email protected]>

* Update src-docs/src/views/datagrid/datagrid_memory_example.js

Co-Authored-By: Greg Thompson <[email protected]>

* Update src-docs/src/views/datagrid/datagrid_schema_example.js

Co-Authored-By: Greg Thompson <[email protected]>

* Update src-docs/src/views/datagrid/datagrid_memory_example.js

Co-Authored-By: Greg Thompson <[email protected]>

* Update src-docs/src/views/datagrid/datagrid_memory_example.js

Co-Authored-By: Greg Thompson <[email protected]>

* Update src-docs/src/views/datagrid/datagrid_memory_example.js

Co-Authored-By: Greg Thompson <[email protected]>

* Updated some datagrid docs

* main dg example page feedback

* Eui prefix all the things to be consistant. Adjust the data grid docs to match

* rewrite intro based on feedback

* more tweaking of words

* rename toolbarDisplay->toolbarVisibility

* in memory docs reworked to four examples

* clean up core example

* data grid styling snippets

* fix prop list

* Minor grammar edits

* Added isDetails prop to renderCellValue, reducing the use case for expansionFormatters. Speaking of those, expansionFormatter(s) has been renamed to popoverContent(s) and now recieve the rendered cell div in addition to the renderCellValue ReactElement

* fix docs renaming, fix css

* last docs edit seems fitting

* somewhat decent attempt at putting classnames on schemas

* Revert "somewhat decent attempt at putting classnames on schemas"

This reverts commit 26542d7.

* changelog
  • Loading branch information
chandlerprall authored Oct 24, 2019
1 parent f879d12 commit b64d503
Show file tree
Hide file tree
Showing 95 changed files with 10,040 additions and 124 deletions.
6 changes: 5 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ module.exports = {
plugins: [
"jsx-a11y",
"prettier",
"local"
"local",
"react-hooks"
],
rules: {
"prefer-template": "error",
Expand Down Expand Up @@ -64,6 +65,9 @@ module.exports = {
"jsx-a11y/tabindex-no-positive": "error",
"jsx-a11y/label-has-associated-control": "error",

// "react-hooks/rules-of-hooks": "error",
// "react-hooks/exhaustive-deps": "warn",

"@typescript-eslint/array-type": ["error", "array-simple"],
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/class-name-casing": "off",
Expand Down
19 changes: 18 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
## [`master`](https://github.com/elastic/eui/tree/master)

No public interface changes since `14.7.0`.
* `EuiButtonGroup` and `EuiButtonToggle` now accept `ReactNode` for their label prop instead of string ([#2392](https://github.com/elastic/eui/pull/2392))
* Added `useRenderToText` to `inner_text` service suite to render `ReactNode`s into label text ([#2392](https://github.com/elastic/eui/pull/2392))
* Added icons `tableDensityExpanded`, `tableDensityCompact`, `tableDensityNormal` to `EuiIcon` ([#2230](https://github.com/elastic/eui/pull/2230))
* Added `!important` to the animation of `EuiFocusRing` animation to make sure it is always used ([#2230](https://github.com/elastic/eui/pull/2230))
* Added `expandMini` icon to `EuiIcon` ([#2207](https://github.com/elastic/eui/pull/2366))
* Changed `EuiPopover` to use `role="dialog"` for better screen-reader announcements ([#2207](https://github.com/elastic/eui/pull/2366))
* Added function callback `onTrapDeactivation` to `EuiPopover` for when a focus trap is deactivated ([#2366](https://github.com/elastic/eui/pull/2366))
* Added logic for rendering of focus around `EuiPopover` to counteract a race condition ([#2366](https://github.com/elastic/eui/pull/2366))
* Added `EuiDataGrid` ([#2165](https://github.com/elastic/eui/pull/2165))

**Bug fixes**

* Corrected `lockProps` passdown in `EuiFocusTrap`, specifically to allows `style` to be passed down. ([#2230](https://github.com/elastic/eui/pull/2230))
* Changed `children` property on `I18nTokensShape` type from a single `ReactChild` to now accept an `array` ([#2230](https://github.com/elastic/eui/pull/2230))
* Adjusted the color of `$euiColorHighlight` in dark mode ([#2176](https://github.com/elastic/eui/pull/2176))
* Changed `EuiPopoverFooter` padding to uniformly adjust with the size of the popover ([#2207](https://github.com/elastic/eui/pull/2207))
* Fixed `isDragDisabled` prop usage in `EuiDraggable` ([#2207](https://github.com/elastic/eui/pull/2366))
* Fixed `EuiMutationObserver`'s handling of`onMutation` when that prop's value changes ([#2421](https://github.com/elastic/eui/pull/2421))

## [`14.7.0`](https://github.com/elastic/eui/tree/v14.7.0)

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"react-is": "~16.3.0",
"react-virtualized": "^9.18.5",
"resize-observer-polyfill": "^1.5.0",
"tabbable": "^1.1.0",
"tabbable": "^3.0.0",
"uuid": "^3.1.0"
},
"devDependencies": {
Expand Down Expand Up @@ -130,6 +130,8 @@
"eslint-plugin-prefer-object-spread": "^1.2.1",
"eslint-plugin-prettier": "^3.1.0",
"eslint-plugin-react": "^7.13.0",
"eslint-plugin-react-hooks": "^2.0.1",
"faker": "^4.1.0",
"file-loader": "^1.1.11",
"findup": "^0.1.5",
"fork-ts-checker-webpack-plugin": "^0.4.4",
Expand Down
150 changes: 144 additions & 6 deletions scripts/babel/proptypes-from-ts-props/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,66 @@ function resolveArrayToPropTypes(node, state) {
}
}

function stripDoubleQuotes(value) {
return value.replace(/^"?(.*?)"?$/, '$1');
}

/**
* Converts an Omit<X, Y> type to resolveIdentifierToPropTypes(X) with Y removed
* @param node
* @param state
* @returns AST node representing matching proptypes
*/
function resolveOmitToPropTypes(node, state) {
const types = state.get('types');

const { typeParameters } = node;

if (typeParameters == null) return null;

const {
params: [sourceType, toRemove],
} = typeParameters;

const sourcePropTypes = getPropTypesForNode(sourceType, true, state);
// validate that this resulted in a shape, otherwise we don't know how to extract/merge the values
if (
!types.isCallExpression(sourcePropTypes) ||
!types.isMemberExpression(sourcePropTypes.callee) ||
sourcePropTypes.callee.object.name !== 'PropTypes' ||
sourcePropTypes.callee.property.name !== 'shape'
) {
return null;
}

const toRemovePropTypes = getPropTypesForNode(toRemove, true, state);
// validate that this resulted in a oneOf, otherwise we don't know how to use the values
if (
!types.isCallExpression(toRemovePropTypes) ||
!types.isMemberExpression(toRemovePropTypes.callee) ||
toRemovePropTypes.callee.object.name !== 'PropTypes' ||
toRemovePropTypes.callee.property.name !== 'oneOf'
) {
return null;
}

// extract the string values of keys to remove
const keysToRemove = new Set(
toRemovePropTypes.arguments[0].elements
.map(keyToRemove =>
types.isStringLiteral(keyToRemove) ? keyToRemove.value : null
)
.filter(x => x !== null)
);

// filter out omitted properties
sourcePropTypes.arguments[0].properties = sourcePropTypes.arguments[0].properties.filter(
({ key: { name } }) => keysToRemove.has(stripDoubleQuotes(name)) === false
);

return sourcePropTypes;
}

/**
* Converts an X[] type to PropTypes.arrayOf(X)
* @param node
Expand Down Expand Up @@ -160,9 +220,16 @@ function resolveIdentifierToPropTypes(node, state) {
types.identifier('PropTypes'),
types.identifier('node')
);

case 'JSXElementConstructor':
return types.memberExpression(
types.identifier('PropTypes'),
types.identifier('func') // this is more accurately `elementType` but our peerDependency version of prop-types doesn't have it
);
}

if (identifier.name === 'Array') return resolveArrayToPropTypes(node, state);
if (identifier.name === 'Omit') return resolveOmitToPropTypes(node, state);
if (identifier.name === 'MouseEventHandler')
return buildPropTypePrimitiveExpression(types, 'func');
if (identifier.name === 'Function')
Expand Down Expand Up @@ -356,10 +423,39 @@ function getPropTypesForNode(node, optional, state) {
propType = getPropTypesForNode(node.typeAnnotation, true, state);
break;

// Foo['bar']
case 'TSIndexedAccessType':
// verify the type of index access
if (types.isTSLiteralType(node.indexType) === false) break;

const indexedName = node.indexType.literal.value;
const objectPropType = getPropTypesForNode(node.objectType, true, state);

// verify this came out as a PropTypes.shape(), which we can pick the indexed property off of
if (
types.isCallExpression(objectPropType) &&
types.isMemberExpression(objectPropType.callee) &&
types.isIdentifier(objectPropType.callee.object) &&
objectPropType.callee.object.name === 'PropTypes' &&
types.isIdentifier(objectPropType.callee.property) &&
objectPropType.callee.property.name === 'shape'
) {
const shapeProps = objectPropType.arguments[0].properties;
for (let i = 0; i < shapeProps.length; i++) {
const prop = shapeProps[i];
if (prop.key.name === indexedName) {
propType = makePropTypeOptional(types, prop.value);
break;
}
}
}

break;

// translates intersections (Foo & Bar & Baz) to a shape with the types' members (Foo, Bar, Baz) merged together
case 'TSIntersectionType':
const usableNodes = node.types.filter(node => {
const nodePropTypes = getPropTypesForNode(node, true, state);
const usableNodes = [...node.types].filter(node => {
let nodePropTypes = getPropTypesForNode(node, true, state);

if (
types.isMemberExpression(nodePropTypes) &&
Expand All @@ -369,12 +465,12 @@ function getPropTypesForNode(node, optional, state) {
return false;
}

// validate that this resulted in a shape, otherwise we don't know how to extract/merge the values
// validate that this resulted in a shape or oneOfType, otherwise we don't know how to extract/merge the values
if (
!types.isCallExpression(nodePropTypes) ||
!types.isMemberExpression(nodePropTypes.callee) ||
nodePropTypes.callee.object.name !== 'PropTypes' ||
nodePropTypes.callee.property.name !== 'shape'
(nodePropTypes.callee.property.name !== 'shape' && nodePropTypes.callee.property.name !== 'oneOfType')
) {
return false;
}
Expand All @@ -384,7 +480,49 @@ function getPropTypesForNode(node, optional, state) {

// merge the resolved proptypes for each intersection member into one object, mergedProperties
const mergedProperties = usableNodes.reduce((mergedProperties, node) => {
const nodePropTypes = getPropTypesForNode(node, true, state);
let nodePropTypes = getPropTypesForNode(node, true, state);

// if this is a `oneOfType` extract those properties into a `shape`
if (
types.isCallExpression(nodePropTypes) &&
types.isMemberExpression(nodePropTypes.callee) &&
nodePropTypes.callee.object.name === 'PropTypes' &&
nodePropTypes.callee.property.name === 'oneOfType'
) {
const properties = nodePropTypes.arguments[0].elements
.map(propType => {
// This exists on a oneOfType which must be expressed as an optional proptype
propType = makePropTypeOptional(types, propType);

// validate we're working with a shape, otherwise we can't merge properties
if (
!types.isCallExpression(propType) ||
!types.isMemberExpression(propType.callee) ||
propType.callee.object.name !== 'PropTypes' ||
propType.callee.property.name !== 'shape'
) {
return null;
}

// extract all of the properties from this group and make them optional
return propType.arguments[0].properties.map(property => {
property.value = makePropTypeOptional(types, property.value);
return property;
});
})
.filter(x => x !== null)
.reduce((allProperties, properties) => {
return [...allProperties, ...properties];
}, []);

nodePropTypes = types.callExpression(
types.memberExpression(
types.identifier('PropTypes'),
types.identifier('shape')
),
[types.objectExpression(properties)]
);
}

// iterate over this type's members, adding them (and their comments) to `mergedProperties`
const typeProperties = nodePropTypes.arguments[0].properties; // properties on the ObjectExpression passed to PropTypes.shape()
Expand Down Expand Up @@ -1062,7 +1200,7 @@ function getPropTypesNodeFromAST(node, types) {
const buildPropTypes = babelTemplate('COMPONENT_NAME.propTypes = PROP_TYPES');

/**
* Called with a type definition and a React component node path, `processComponentDeclaration` translates that definiton
* Called with a type definition and a React component node path, `processComponentDeclaration` translates that definition
* to an AST of PropType.* calls and attaches those prop types to the component.
* @param typeDefinition
* @param path
Expand Down
49 changes: 49 additions & 0 deletions scripts/babel/proptypes-from-ts-props/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,28 @@ FooComponent.propTypes = {
};`);
});

it('understands JSXElementConstructor', () => {
const result = transform(
`
import React from 'react';
const FooComponent: React.SFC<{foo: JSXElementConstructor<any>}> = () => {
return (<div>Hello World</div>);
}`,
babelOptions
);

expect(result.code).toBe(`import React from 'react';
import PropTypes from "prop-types";
const FooComponent = () => {
return <div>Hello World</div>;
};
FooComponent.propTypes = {
foo: PropTypes.func.isRequired
};`);
});

});

describe('node propType', () => {
Expand Down Expand Up @@ -2051,6 +2073,33 @@ FooComponent.propTypes = {

});

describe('indexed property access', () => {
it('follows indexed properties', () => {
const result = transform(
`
import React from 'react';
interface Foo {
foo: () => {};
}
const FooComponent: React.SFC<{bar: Foo['foo']}> = () => {
return (<div>Hello World</div>);
}`,
babelOptions
);

expect(result.code).toBe(`import React from 'react';
import PropTypes from "prop-types";
const FooComponent = () => {
return <div>Hello World</div>;
};
FooComponent.propTypes = {
bar: PropTypes.func.isRequired
};`);
});
});

describe('supported component declarations', () => {

it('annotates React.SFC components', () => {
Expand Down
8 changes: 8 additions & 0 deletions src-docs/src/components/guide_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,11 @@ $guideZLevelHighest: $euiZLevel9 + 1000;
}
}
}

.euiDataGridRowCell--favoriteFranchise {
background: transparentize($color: #800080, $amount: .95) !important;
}

.euiDataGridHeaderCell--favoriteFranchise {
background: transparentize($color: #800080, $amount: .9) !important;
}
10 changes: 0 additions & 10 deletions src-docs/src/components/guide_section/_guide_section.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,3 @@
.guideSection__space {
height: $euiSizeL;
}

.guideSectionPropsTable {
width: auto;
min-width: 50%;

th,
td {
max-width: none;
}
}
Loading

0 comments on commit b64d503

Please sign in to comment.