Rendering an action menu in Ant Table on Row hover - reactjs

I'm using Ant table to display some data. So far so good except the requirement that I need to display an action menu when a particular row hovered over. Here's the mock of what I'm trying to achieve:
Ant table's onRow callback will allow me to get the record that's being hovered over and onRowClassName allows me to pass a class name so I can dynamically apply css on the row hovered over. But I'm stuck on rendering an element at the end of the row like you see in the screenshot.
I'm a bit stumbled on how to go about doing this. Closest thing I came across is this context menu implementation: https://codesandbox.io/s/rm23kroqyo
Appreciate any input.

One way to do this is to add a column for the actions and add a className for it.
export const columns = [
{ title: `Name`, dataIndex: `name` },
{ title: `Age`, dataIndex: `age` },
{ title: `Address`, dataIndex: `address` },
{
title: "",
dataIndex: "actions",
render: (actions) =>
actions &&
actions.map((action) => (
<a className="action" href>
{action}
</a>
)),
className: "actions"
}
];
add the actions on the data
const dataWithActions = data.map((item) =>
item.key === "2" ? { ...item, actions: ["Like", "Share"] } : item
);
Then set it's position absolute so it does not take up space
.actions {
position: absolute;
}
.ant-table-row {
position: relative;
}
And finally position it when the row is hovered
.ant-table-row:hover .actions {
display: block;
height: 54px;
right: 0;
}
Here's the updated codesandbox:
https://codesandbox.io/s/custom-context-menu-table-antd-forked-b6y5c

Related

Mui Datagrid not populating, data is there

I have a few Mui datagrids through my app. I recently switched over to using RTK Query for the api and was able to get all the grids functioning and displaying fine. I honestly do not know what changed (since it took place after I was done mucking with the grid components and was adjusting the auth/login to RTK) but 3 of the 4 are displaying an empty grid with no errors. I can console the data, and even the corner of the grids shows the total number of entries, which reflects the changes if I add an item to the array that should be displayed by the grid. I already had the container components set to display: flex and set autoHeight and autoWidth on the grids. I've tried adjusting the containers, even putting height to 5000, with no change. As I mentioned, I have 1 that still displays correctly, and even copying the display props for that grid to the others had no effect.
import React from 'react';
import PropTypes from 'prop-types';
import { DataGrid, GridActionsCellItem } from '#mui/x-data-grid';
import { Link } from 'react-router-dom';
import { useGetVisitsByUserIdQuery } from '../../redux/apiSlice';
import { CircularProgress, Box } from '#mui/material';
const UserVisits = ({ user }) => {
const userId = user._id
const {
data,
isLoading,
isSuccess
} = useGetVisitsByUserIdQuery(userId);
console.log(data)
const columns =
[
{
field: 'client',
headerName: 'Client',
width: 150,
renderCell: (params) => {
return (
<Link to={`/ClientChart/${params?.value?._id}`}>{params?.value?.fullName}</Link>
)}
},
{
field: 'visitStart',
headerName: 'Start Time',
type: 'date',
width: 200,
valueFormatter: (params) => {
const date = new Date(params.value);
let options = {
year: "numeric",
month: "numeric",
day: "numeric"
};
return date.toLocaleTimeString("en-US", options);
}
},
{
field: 'visitEnd',
headerName: 'End Time',
type: 'date',
width: 200,
valueFormatter: (params) => {
const date = new Date(params.value);
let options = {
year: "numeric",
month: "numeric",
day: "numeric"
};
return date.toLocaleTimeString("en-US", options);
}
},
{field: 'location', headerName: 'Location', width: 150},
];
let content
if (isLoading) {
content = <CircularProgress />
} else if (isSuccess) {
content =
<div style={{ display: 'flex', height: '100%'}}>
<Box sx={{ flexGrow: 1 }}>
{ data &&
<DataGrid
autoHeight
autoWidth
getRowId={(row) => row.id}
rows={data}
columns={columns}
rowsPerPageOptions={[20, 50, 100]}
autoPageSize
//sx={{ height: '100%', width: '100%' }}
/>}
</Box>
</div>
}
return (
<div>
<h1>Visits</h1>
<div>
{content}
</div>
</div>
)
}
If I push new data to the grid, the number of entries in the bottom right corner adjusts, and the console shows the correct array of data, as well as Redux DevTools showing the data in state. Even manually adjusting the height of the div/Box containing the grid, the grid itself never changes in height.
Again they had been working perfectly, and I'm at a total loss as to what would have affected them. Any help is greatly appreciated.
Thanks
So the issue was resolved by removing 'autoPageSize' from the Datagrid props. Playing with it, it seems that autoHeight and autoPageSize cause the grid to break when in place together.
Could not find any specific references online to this being an issue, but I was able to recreate the issue on this codesandbox I found: https://codesandbox.io/s/datagrid-v5-0-1-autoheight-autopagesize-9dy64?file=/src/App.tsx
If you add in autoPageSize to the datagrid props, the grid goes empty.

Material UI Stepper 4 Different Connector Color at 4 different steps

I was wondering if it was possible to have multiple connector colors with a different one at each step. So the first connector would be blue the one after that would be green then yellow then red all while keeping the previous color the same. The closest have gotten changes all previous colors to the same color. Is there a way to keep the previous the same color?
The Example I linked only has connectors of one color
Example of Stepper with Connector only one color
This answer shows how to change the color of individual StepIcons
Given that you have a function outside the component rendering the Stepper that returns an array containing the step labels and their corresponding icon color:
function getStepLabels() {
return [
{
label: "Select campaign settings",
color: "red"
},
{
label: "Create an ad group",
color: "blue"
},
{
label: "Create an ad",
color: "green"
}
];
}
you can generate classes for each label icon using material-ui's Hook API via the makeStyles function (if you are using a class component you might want to take a look at the withStyles function):
const useIconStyles = makeStyles(
(() => {
return getStepLabels().reduce((styles, step, index) => {
styles[index] = { color: `${step.color} !important` };
return styles;
}, {});
})()
);
and add the generated hook to your component: const iconClasses = useIconStyles();
When rendering the stepper you can make use of the generated classes like this:
[...]
<Step key={label} {...stepProps}>
<StepLabel
{...labelProps}
StepIconProps={{ classes: { root: iconClasses[index] } }}
>
{label}
</StepLabel>
</Step>
[...]
Here is a working example:
If you take a closer look at the render output of the Stepper component you will notice that StepLabel and StepConnector are sibling components. This means you can select a specific connector with the CSS :nth-child() pseudo-class. If you want to select the connector after the first step's label you would use the selector :nth-child(2). For the connector after second step's label it would be :nth-child(4).
If you have an array of step labels like this:
[
{
label: "First label",
connectorColor: "red"
},
{
label: "Second label",
connectorColor: "green"
},
{
label: "Third label"
}
];
you can pass this array to a material-ui style hook created by the makeStyles function and dynamically generate all the different CSS selectors necessary to style each connector:
const useConnectorStyles = makeStyles({
stepConnector: steps => {
const styles = {};
steps.forEach(({ connectorColor }, index) => {
if (index < steps.length - 1) {
styles[`&:nth-child(${2 * index + 2})`] = { color: connectorColor };
}
});
return styles;
},
stepConnectorLine: {
borderColor: "currentColor"
}
});
Now use the generated style hook in your component: const connectorClasses = useConnectorStyles(stepLabels); and provide the connector prop to the Stepper component:
connector={
<StepConnector
classes={{
root: connectorClasses.stepConnector,
line: connectorClasses.stepConnectorLine
}}
/>
}
Here is a working example:

React - Antd - Show/Hide Columns in table

I would like to resolve one problem.
How can I show/hide columns in table using Ant Design in React?
export const columns = () => [
{
key: "anyKeyOne",
title: "Title one",
dataSource: "AnyOne",
hide: true
},
{
key: "anyKeyTwo",
title: "TitleTwo",
dataSource: "AnyTwo",
hide: false
}
]
hideColumns = () => {
//
}
render() {
return (
<div>
<Table
dataSource={store.data}
columns={this.hideColumns}
/>
</div>
)
}
Thank you for answers.
You can set a boolean state property like hideColumn
<div>
<Table
dataSource={store.data}
columns={this.state.hideColumn? this.state.columns: this.state.columns}
/>
</div>
Use this function to build your visible column array. It uses dataIndex to compare the column name needed to be shown.
Form the arrayOfColumnNeeded by pushing values from a checkbox group maybe.
let columnsDisplayed = _.remove(columns, function(n) {
return arrayOfColumnsNeeded.includes(n.dataIndex);
});
You can add a className field in the object and add a css property 'display: none' to that class
{
key: "anyKeyOne",
title: "Title one",
dataSource: "AnyOne",
className: "hide"
}

Render a component oclick of a row in react-table (https://github.com/react-tools/react-table)

I want to render a component when a row is clicked in a react-table. I know i can use a subcomponent to achieve this but that doesn't allow click on the entire row. I want the subcomponent to render when the user clicks anywhere on that row. From their github page i understand that i want to edit getTdProps but am not really able to achieve it. Also the subcomponent is form and on the save of that form i want to update that row to reflect the changes made by the user and close the form. Any help is appreciated.
import React, {Component} from 'react';
import AdomainRow from './AdomainRow'
import ReactTable from "react-table"
import AdomainForm from './AdomainForm'
import 'react-table/react-table.css'
export default class AdomianTable extends Component {
render() {
const data = [{
adomain: "Reebok1.com",
name: "Reebok",
iabCategories: ["IAB1", "IAB2", "IAB5"],
status: "PENDING",
rejectionType: "Offensive Content",
rejectionComment: "The content is offensive",
isGeneric: false,
modifiedBy: "Sourav.Prem"
},
{
adomain: "Reebok2.com",
name: "Reebok",
iabCategories: ["IAB1", "IAB2", "IAB5"],
status: "PENDING",
rejectionType: "Offensive Content",
rejectionComment: "The content is offensive",
isGeneric: false,
modifiedBy: "Sourav.Prem"
},
{
adomain: "Reebok3.com",
name: "Reebok",
iabCategories: ["IAB1", "IAB2", "IAB5"],
status: "PENDING",
rejectionType: "Offensive Content",
rejectionComment: "The content is offensive",
isGeneric: false,
modifiedBy: "Sourav.Prem"
}];
//FOR REACT TABLE TO WORK
const columns = [{
Header : 'Adomian',
accessor : 'adomain'
}, {
Header : 'Name',
accessor : 'name'
}, {
Header : 'IABCategories',
accessor : 'iabCategories',
Cell : row => <div>{row.value.join(", ")}</div>
}, {
Header : 'Status',
accessor : 'status'
}];
return (
<div>
<ReactTable
getTdProps={(state, rowInfo, column, instance) => {
return {
onClick: (e, handleOriginal) => {
<AdomainForm row={rowInfo} ></AdomainForm>
console.log('A Td Element was clicked!')
console.log('it produced this event:', e)
console.log('It was in this column:', column)
console.log('It was in this row:', rowInfo)
console.log('It was in this table instance:', instance)
// IMPORTANT! React-Table uses onClick internally to trigger
// events like expanding SubComponents and pivots.
// By default a custom 'onClick' handler will override this functionality.
// If you want to fire the original onClick handler, call the
// 'handleOriginal' function.
if (handleOriginal) {
handleOriginal()
}
}
}
}}
data={data.adomains}
columns={columns}
defaultPageSize={10}
className="footable table table-stripped toggle-arrow-tiny tablet breakpoint footable-loaded"
SubComponent = { row =>{
return (
<AdomainForm row={row} ></AdomainForm>
);
}}
/>
</div>
);
}
}
}
I ran into the same issue where I wanted the entire row to be a clickable expander as React Table calls it. What I did was simply change the dimensions of the expander to match the entire row and set a z-index ahead of the row. A caveat of this approach is that any clickable elements you have on the row itself will now be covered by a full width button. My case had display only elements in the row so this approach worked.
.rt-expandable {
position: absolute;
width: 100%;
max-width: none;
height: 63px;
z-index: 1;
}
To remove the expander icon you can simply do this:
.rt-expander:after {
display: none;
}
I found it was better to add a classname to the react table:
<AdvancedExpandReactTable
columns={[
{
Header: InventoryHeadings.PRODUCT_NAME,
accessor: 'productName',
},
]}
className="-striped -highlight available-inventory" // custom classname added here
SubComponent={({ row, nestingPath, toggleRowSubComponent }) => (
<div className={classes.tableInner}>
{/* sub component goes here... */}
</div>
)}
/>
Then modify the styles so that the columns line up
.available-inventory .-header,
.available-inventory .-filters {
margin-left: -40px;
}
And then modify these styles as Sven suggested:
.rt-tbody .rt-expandable {
cursor: pointer;
position: absolute;
width: 100% !important;
max-width: none !important;
}
.rt-expander:after{
display: none;
}

Antd: Is it possible to move the table row expander inside one of the cells?

I have an antd table where the data inside one of the columns can get pretty large. I am showing this data in full when the row is expanded but because the cell with a lot of data is on the right side of the screen and the expander icon is on the left side of the screen it is not very intuitive. What I would like to do is move the expander icon inside the actual cell so that the user knows they can click the + to see the rest of the data.
Thanks in advance.
Yes, you can and you have to dig a little deeper their docucmentation...
According to rc-table docs you can use expandIconColumnIndex for the index column you want to add the +, also you have to add expandIconAsCell={false} to make it render as part of the cell.
See Demo
This is how you can make any column expendable.
First add expandedRowKeys in your component state
state = {
expandedRowKeys: [],
};
Then you need to add these two functions onExpand and updateExpandedRowKeys
<Table
id="table-container"
rowKey={record => record.rowKey}
className={styles['quote-summary-table']}
pagination={false}
onExpand={this.onExpand}
expandedRowKeys={this.state.expandedRowKeys}
columns={columns({
updateExpandedRowKeys: this.updateExpandedRowKeys,
})
}
dataSource={this.data}
oldTable={false}
/>
This is how you need to define the function so
that in expandedRowKeys we will always have
updates values of expanded rowKeys
onExpand = (expanded, record) => {
this.updateExpandedRowKeys({ record });
};
updateExpandedRowKeys = ({ record }) => {
const rowKey = record.rowKey;
const isExpanded = this.state.expandedRowKeys.find(key => key === rowKey);
let expandedRowKeys = [];
if (isExpanded) {
expandedRowKeys = expandedRowKeys.reduce((acc, key) => {
if (key !== rowKey) acc.push(key);
return acc;
}, []);
} else {
expandedRowKeys.push(rowKey);
}
this.setState({
expandedRowKeys,
});
}
And finally, you need to call the function updateExpandedRowKeys
for whichever column you want to have the expand-collapse functionality available.
Even it can be implemented for multiple columns.
export const columns = ({
updateExpandedRowKeys,
}) => {
let columnArr = [
{
title: 'Product',
key: 'productDes',
dataIndex: 'productDes',
className: 'productDes',
render: (text, record) => (
<span onClick={rowKey => updateExpandedRowKeys({ record })}>
{text}
</span>
),
}, {
title: 'Product Cat',
key: 'productCat',
dataIndex: 'productCat',
className: 'product-Cat',
}]
}

Resources