Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ResultTable: HeaderCell and Cell rendering tweaks #210

Merged
merged 8 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading