Skip to content

Commit

Permalink
Merge pull request #210 from smartprocure/cell-rendering-tweaks
Browse files Browse the repository at this point in the history
ResultTable: HeaderCell and Cell rendering tweaks
  • Loading branch information
stellarhoof authored Jun 18, 2024
2 parents 1f44de1 + 6d0e89a commit 9fc5d2c
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 129 deletions.
8 changes: 8 additions & 0 deletions .changeset/light-planets-care.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'contexture-react': patch
---

ResultTable: HeaderCell and Cell rendering tweaks

- Do not wrap `HeaderCell` children in a `span` by default because it makes providing a custom `HeaderCell` harder.
- Support `headerCellProps` and `cellProps` to accomodate the very common pattern of needing to only pass props to the header/cell components.
223 changes: 114 additions & 109 deletions packages/react/src/exampleTypes/ResultTable/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import _ from 'lodash/fp.js'
import F from 'futil'
import { observer } from 'mobx-react'
import { Dynamic } from '../../greyVest/index.js'
import { Dynamic, Flex } from '../../greyVest/index.js'
import { withTheme } from '../../utils/theme.js'

const moveColumn = (
Expand Down Expand Up @@ -67,7 +67,16 @@ let Header = ({
hideMenu,
typeDefault,
} = fieldSchema
let HeaderCell = fieldSchema.HeaderCell || Th
let HeaderCell =
fieldSchema.HeaderCell ||
(({ children, ...props }) => (
<Th {...props}>
<Flex style={{ display: 'inline-flex', alignItems: 'center' }}>
{children}
</Flex>
</Th>
))

let filterNode =
criteria &&
_.find({ field }, _.getOr([], 'children', tree.getNode(criteria)))
Expand All @@ -81,136 +90,132 @@ let Header = ({
F.flip(filtering)()
}
let Label = label
let { className, style, ...headerCellProps } =
fieldSchema.headerCellProps ?? {}
return (
<HeaderCell
className={`${isStickyColumn ? 'sticky-column-header' : ''}
${_.get('hasValue', filterNode) ? 'active-filter' : ''}`}
${_.get('hasValue', filterNode) ? 'active-filter' : ''} ${className}`}
style={{
cursor: hideMenu ? 'default' : 'pointer',
left: isStickyColumn ? 0 : '',
...style,
}}
{...headerCellProps}
>
<span>
{_.isFunction(label) ? <Label /> : label}{' '}
{field === node.sortField && (
<Icon
icon={node.sortDir === 'asc' ? 'SortAscending' : 'SortDescending'}
/>
)}
<Popover
trigger={hideMenu ? null : <Icon icon="TableColumnMenu" />}
position={`bottom ${isLastColumn ? 'right' : 'center'}`}
closeOnPopoverClick={false}
style={popoverStyle}
>
{!disableSort && (
<DropdownItem
onClick={() => {
mutate({ sortField, sortDir: 'asc' })
}}
>
{_.isFunction(label) ? <Label /> : label}{' '}
{field === node.sortField && (
<Icon
icon={node.sortDir === 'asc' ? 'SortAscending' : 'SortDescending'}
/>
)}
<Popover
trigger={hideMenu ? null : <Icon icon="TableColumnMenu" />}
position={`bottom ${isLastColumn ? 'right' : 'center'}`}
closeOnPopoverClick={false}
style={popoverStyle}
>
{!disableSort && (
<>
<DropdownItem onClick={() => mutate({ sortField, sortDir: 'asc' })}>
<Icon icon="SortAscending" />
Sort Ascending
</DropdownItem>
)}
{!disableSort && (
<DropdownItem
onClick={() => {
mutate({ sortField, sortDir: 'desc' })
}}
onClick={() => mutate({ sortField, sortDir: 'desc' })}
>
<Icon icon="SortDescending" />
Sort Descending
</DropdownItem>
)}
</>
)}
<DropdownItem
onClick={() =>
moveColumn(mutate, (i) => i - 1, field, visibleFields, includes)
}
>
<Icon icon="MoveLeft" />
Move Left
</DropdownItem>
<DropdownItem
onClick={() =>
moveColumn(mutate, (i) => i + 1, field, visibleFields, includes)
}
>
<Icon icon="MoveRight" />
Move Right
</DropdownItem>
{!hideRemoveColumn && (
<DropdownItem
onClick={() =>
moveColumn(mutate, (i) => i - 1, field, visibleFields, includes)
}
onClick={() => mutate({ include: _.without([field], includes) })}
>
<Icon icon="MoveLeft" />
Move Left
<Icon icon="RemoveColumn" />
Remove Column
</DropdownItem>
<DropdownItem
onClick={() =>
moveColumn(mutate, (i) => i + 1, field, visibleFields, includes)
}
>
<Icon icon="MoveRight" />
Move Right
)}
{!!addOptions.length && (
<DropdownItem onClick={F.on(adding)}>
<Icon icon="AddColumn" />
Add Column
</DropdownItem>
{!hideRemoveColumn && (
<DropdownItem
onClick={() => mutate({ include: _.without([field], includes) })}
>
<Icon icon="RemoveColumn" />
Remove Column
</DropdownItem>
)}
{!!addOptions.length && (
<DropdownItem onClick={F.on(adding)}>
<Icon icon="AddColumn" />
Add Column
)}
{criteria && (typeDefault || filterNode) && !disableFilter && (
<div>
<DropdownItem onClick={filter}>
<Icon
icon={
filterNode
? F.view(filtering)
? 'FilterCollapse'
: 'FilterExpand'
: 'FilterAdd'
}
/>
Filter
</DropdownItem>
)}
{criteria && (typeDefault || filterNode) && !disableFilter && (
<div>
<DropdownItem onClick={filter}>
<Icon
icon={
filterNode
? F.view(filtering)
? 'FilterCollapse'
: 'FilterExpand'
: 'FilterAdd'
}
{F.view(filtering) && filterNode && !filterNode.paused && (
<>
<Dynamic
{...{
component: UnmappedNodeComponent,
tree,
path: _.toArray(filterNode.path),
...mapNodeToProps(filterNode, fields),
}}
/>
Filter
</DropdownItem>
{F.view(filtering) && filterNode && !filterNode.paused && (
<>
<Dynamic
{...{
component: UnmappedNodeComponent,
tree,
path: _.toArray(filterNode.path),
...mapNodeToProps(filterNode, fields),
{tree.disableAutoUpdate && node.markedForUpdate && (
<Button
primary
style={{ width: '100%' }}
onClick={(e) => {
e.stopPropagation()
tree.triggerUpdate()
}}
/>
{tree.disableAutoUpdate && node.markedForUpdate && (
<Button
primary
style={{ width: '100%' }}
onClick={(e) => {
e.stopPropagation()
tree.triggerUpdate()
}}
>
Search
</Button>
)}
</>
)}
</div>
)}
<Modal open={adding}>
<NestedPicker
itemType="column"
options={addOptions}
onChange={(selectedFields) => {
_.each(({ field: addedField }) => {
let index = includes.indexOf(field)
if (index >= 0 && addedField) {
includes.splice(index + 1, 0, addedField)
mutate({ include: includes })
}
}, selectedFields)
F.off(adding)()
}}
/>
</Modal>
</Popover>
</span>
>
Search
</Button>
)}
</>
)}
</div>
)}
<Modal open={adding}>
<NestedPicker
itemType="column"
options={addOptions}
onChange={(selectedFields) => {
_.each(({ field: addedField }) => {
let index = includes.indexOf(field)
if (index >= 0 && addedField) {
includes.splice(index + 1, 0, addedField)
mutate({ include: includes })
}
}, selectedFields)
F.off(adding)()
}}
/>
</Modal>
</Popover>
</HeaderCell>
)
}
Expand Down
13 changes: 11 additions & 2 deletions packages/react/src/exampleTypes/ResultTable/TableBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,23 @@ let TableBody = ({
{...{ fields, visibleFields, hiddenFields }}
>
{_.map(
({ field, display = (x) => x, Cell = Td }) => (
({
field,
display = (x) => x,
Cell = Td,
cellProps: { className, style, ...cellProps } = {},
}) => (
<Cell
key={field}
className={field === stickyColumn ? 'sticky-column' : ''}
className={`${
field === stickyColumn ? 'sticky-column' : ''
} ${className}`}
style={{
position: field === stickyColumn ? 'sticky' : '',
left: field === stickyColumn ? 0 : '',
...style,
}}
{...cellProps}
>
{defaultDisplay({
display,
Expand Down
39 changes: 28 additions & 11 deletions packages/react/src/exampleTypes/ResultTable/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,25 @@ let ResultTable = ({
)(visibleFields)

let columnGroups = _.reduce(
(columnGroups, { fieldGroup, HeaderCell, HeaderGroup }) => {
(
columnGroups,
{ fieldGroup, headerCellProps, HeaderCell, HeaderGroup }
) => {
for (let i = 0; i < columnGroupsHeight; i++) {
let groupRow = columnGroups[i] || (columnGroups[i] = [])
let groupName = _.getOr('', i, fieldGroup)
let lastGroup = _.last(groupRow)
if (_.get('groupName', lastGroup) === groupName) {
lastGroup.colspan++
lastGroup.HeaderCell = HeaderCell
} else groupRow.push({ groupName, colspan: 1, HeaderCell, HeaderGroup })
} else
groupRow.push({
groupName,
colspan: 1,
headerCellProps,
HeaderCell,
HeaderGroup,
})
}
return columnGroups
},
Expand All @@ -122,15 +132,22 @@ let ResultTable = ({
(columnGroupRow, i) => (
<Tr key={i}>
{F.mapIndexed(
({ groupName, colspan, HeaderCell = Th, HeaderGroup }, j) => (
<HeaderCell key={j} colSpan={colspan}>
<span>
{HeaderGroup ? (
<HeaderGroup>{groupName}</HeaderGroup>
) : (
groupName
)}
</span>
(
{
groupName,
colspan,
headerCellProps,
HeaderCell = Th,
HeaderGroup,
},
j
) => (
<HeaderCell key={j} colSpan={colspan} {...headerCellProps}>
{HeaderGroup ? (
<HeaderGroup>{groupName}</HeaderGroup>
) : (
groupName
)}
</HeaderCell>
),
columnGroupRow
Expand Down
12 changes: 5 additions & 7 deletions packages/react/src/greyVest/Style.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,16 +139,14 @@ export default () => (
/* Go on top of table content */
z-index: 2;
}
.gv-table th.sticky-column-header {
/* Go on top of table headers */
z-index: 3;
}
.gv-table th > span {
display: flex;
align-items: center;
.gv-table th > *:first-child {
box-sizing: border-box;
padding: ${tableCellPadding}px;
min-height: 45px;
Expand Down Expand Up @@ -194,14 +192,14 @@ export default () => (
z-index: 1;
}
.gv-table th.sticky-column-header + th > span,
.gv-table th.sticky-column-header + th > *:only-child,
.gv-table td.sticky-column + td,
.gv-table th:first-child > span ,
.gv-table th:first-child > *:only-child ,
.gv-table td:first-child {
padding-left: ${2 * tableCellPadding}px;
}
.gv-table th:last-child > span ,
.gv-table th:last-child > *:only-child ,
.gv-table td:last-child {
padding-right: ${2 * tableCellPadding}px;
}
Expand Down

0 comments on commit 9fc5d2c

Please sign in to comment.