I have been looking for a reliable component with a clear API documentation that would allow me to display a "tree view" structure for a select input as part of a form. The closest I have came across is vue-treeselect with many supported features such as: disabling branch nodes, disable item selection and more; the issue is that it's only available on Vue JS. My project is using Material UI as its design system, any component that supports it would be very great. Thanks
I needed a to deal with tree data in a project as well. I ended up creating MUI Tree Select.
You can demo it in this sandbox.
I searched a lot for that in the end I made this by myself sandbox.
you can choose to parent or child with this and you can custom it easily.
import { ThemeProvider, createTheme } from "#mui/material/styles";
import React, { useState } from "react";
import ReactDOM from "react-dom";
import TreeItem from "#mui/lab/TreeItem";
import { Popover, TextField, Typography } from "#mui/material";
import clsx from "clsx";
import { TreeView, useTreeItem } from "#mui/lab";
import ExpandMoreIcon from "#mui/icons-material/ExpandMore";
import ChevronRightIcon from "#mui/icons-material/ChevronRight";
import { useMediaQuery } from "#mui/material";
const data = [
{
id: "root",
name: "Parent",
children: [
{
id: "1",
name: "Child - 1"
},
{
id: "3",
name: "Child - 3",
children: [
{
id: "4",
name: "Child - 4"
}
]
}
]
},
{
id: "1root",
name: "Parent1",
children: [
{
id: "5",
name: "Child - 1-1"
},
{
id: "7",
name: "Child - 3-1",
children: [
{
id: "8",
name: "Child - 4-1"
}
]
}
]
}
];
const CustomContent = React.forwardRef(function CustomContent(props, ref) {
const {
classes,
className,
label,
nodeId,
icon: iconProp,
expansionIcon,
displayIcon
} = props;
const {
disabled,
expanded,
selected,
focused,
handleExpansion,
handleSelection,
preventSelection
} = useTreeItem(nodeId);
const icon = iconProp || expansionIcon || displayIcon;
const handleMouseDown = (event) => {
preventSelection(event);
};
const handleExpansionClick = (event) => {
handleExpansion(event);
};
const handleSelectionClick = (event) => {
handleSelection(event);
};
return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
className={clsx(className, classes.root, {
[classes.expanded]: expanded,
[classes.selected]: selected,
[classes.focused]: focused,
[classes.disabled]: disabled
})}
onMouseDown={handleMouseDown}
ref={ref}
style={{ padding: "3px 0" }}
>
<div onClick={handleExpansionClick} className={classes.iconContainer}>
{icon}
</div>
<Typography
onClick={handleSelectionClick}
component="div"
className={classes.label}
>
{label}
</Typography>
</div>
);
});
const CustomTreeItem = (props) => (
<TreeItem ContentComponent={CustomContent} {...props} />
);
export default function RichObjectTreeView({ formik, edit }) {
const [anchorEl, setAnchorEl] = React.useState(null);
const [equipmentItem, setEquipmentItem] = useState("");
const [equipmentId, setEquipmentId] = useState("");
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
const renderTree = (nodes) => (
<CustomTreeItem key={nodes.id} nodeId={nodes.id} label={nodes.name}>
{Array.isArray(nodes.children)
? nodes.children.map((node) => renderTree(node))
: null}
</CustomTreeItem>
);
return (
<>
<TextField
variant="standard"
required={false}
label="Equipment Item"
name="equipmentItem"
id="equipmentItem"
defaultValue={equipmentItem}
value={equipmentItem}
className="w-100"
inputProps={{ readOnly: !edit }}
onClick={handleClick}
/>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
}}
>
<TreeView
aria-label="icon expansion"
defaultSelected={equipmentId}
selected={equipmentId}
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
onNodeSelect={(e, id) => {
setEquipmentId(id);
setEquipmentItem(e.target.innerText);
}}
sx={{
height: 200,
flexGrow: 1,
minWidth: "200px",
overflowY: "auto"
}}
>
{data.map((item, i) => renderTree(item))}
</TreeView>
</Popover>
</>
);
}
You can install this library here.
it cover both reactive forms and ngforms too
Check this out https://www.npmjs.com/package/mat-tree-select-input
Probably this is what you are looking for:
https://github.com/dowjones/react-dropdown-tree-select (it also has a theme for mui like style)
Related
I want to bring a customized full calnedar and connect it with Google canlander api, but I don't know because it's customized Help me
The full calendar site says we can apply the code below, but I don't know where to put the code.
let calendar = new Calendar(calendarEl, {
plugins: [ googleCalendarPlugin ],
googleCalendarApiKey: '<YOUR API KEY>',
events: {
googleCalendarId: 'abcd1234#group.calendar.google.com',
className: 'gcal-event' // an option!
}
});
Below is my current code.
import React, { useState, useRef } from "react";
import "./style.module.css";
import FullCalendar from "#fullcalendar/react";
import timeGridPlugin from "#fullcalendar/timegrid";
import dayGridPlugin from "#fullcalendar/daygrid";
import interactionPlugin from "#fullcalendar/interaction";
import { nanoid } from "nanoid";
import {
Row,
Col,
Button,
FormGroup,
Label,
Input,
Container
} from "reactstrap";
import Select from "react-select";
import DateRangePicker from "react-bootstrap-daterangepicker";
import googleCalendarPlugin from "#fullcalendar/google-calendar"; //google calendar api
import "./custom.module.css";
import events from "./events";
import CustomModal from "../Components/CustomModal";
let todayStr = new Date().toISOString().replace(/T.*$/, "");
export default function Calendar() {
const [weekendsVisible, setWeekendsVisible] = useState(true);
const [currentEvents, setCurrentEvents] = useState([]);
const [modal, setModal] = useState(false);
const [confirmModal, setConfirmModal] = useState(false);
const calendarRef = useRef(null);
const [title, setTitle] = useState("");
const [start, setStart] = useState(new Date());
const [end, setEnd] = useState(new Date());
const handleCloseModal = () => {
handleClose();
setModal(false);
};
function handleDateClick(arg) {
};
function handleDateSelect(selectInfo) {
if (
selectInfo.view.type === "timeGridWeek" ||
selectInfo.view.type === "timeGridDay"
) {
selectInfo.view.calendar.unselect();
setState({ selectInfo, state: "create" });
// Open modal create
console.log("open modal create");
// console.log(selectInfo);
setStart(selectInfo.start);
setEnd(selectInfo.end);
setModal(true);
}
}
function renderEventContent(eventInfo) {
return (
<div>
<i
style={{
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis"
}}
>
{eventInfo.event.title}
</i>
</div>
);
}
function handleEventClick(clickInfo) {
setState({ clickInfo, state: "update" });
setTitle(clickInfo.event.title);
setStart(clickInfo.event.start);
setEnd(clickInfo.event.end);
setModal(true);
}
function handleEvents(events) {
setCurrentEvents(events);
}
function handleEventDrop(checkInfo) {
setState({ checkInfo, state: "drop" });
setConfirmModal(true);
}
function handleEventResize(checkInfo) {
setState({ checkInfo, state: "resize" });
setConfirmModal(true);
}
function handleEdit() {
state.clickInfo.event.setStart(start);
state.clickInfo.event.setEnd(end);
state.clickInfo.event.mutate({
standardProps: { title }
});
handleClose();
}
function handleSubmit() {
const newEvent = {
id: nanoid(),
title,
start: state.selectInfo?.startStr || start.toISOString(),
end: state.selectInfo?.endStr || end.toISOString(),
allDay: state.selectInfo?.allDay || false
};
calendarApi.addEvent(newEvent);
handleClose();
}
function handleDelete() {
state.clickInfo.event.remove();
handleClose();
}
function handleClose() {
setTitle("");
setStart(new Date());
setEnd(new Date());
setState({});
setModal(false);
}
const [state, setState] = useState({});
const [departments, setDepartments] = useState([
{ value: "1", label: "All" },
{ value: "2", label: "BPA Technical" },
{ value: "3", label: "Aqua 2 Cleaning" }
]);
function onFilter(element) {
console.log(element.value);
}
return (
<div className="App">
<Container>
<Row style={{ marginTop: 150 }}>
<h1 style={{color:'white', textAlign:'center', marginBottom:50}}>Make a Booking</h1>
</Row>
<Row>
<Col md={12}>
<FullCalendar
ref={calendarRef}
plugins={[dayGridPlugin,timeGridPlugin, interactionPlugin]}
headerToolbar={{
left: "prev,today,next",
center: "title",
right: "dayGridMonth,timeGridWeek,timeGridDay"
}}
buttonText={{
today: "Today's Time Table",
month: "month",
week: "week",
day: "day",
list: "list"
}}
initialView="timeGridWeek"
editable={true}
selectable={true}
selectMirror={true}
dayMaxEvents={true}
weekends={weekendsVisible}
//
initialEvents={[
{
},
{
id: nanoid(),
title: "All-day event",
start: todayStr
},
{
id: nanoid(),
title: "Timed event",
start: todayStr + "T12:00:00",
end: todayStr + "T12:30:00"
}
]}
select={handleDateSelect}
eventContent={renderEventContent} // custom render function
eventClick={handleEventClick}
eventsSet={() => handleEvents(events)}
eventDrop={handleEventDrop}
eventResize={handleEventResize}
//
dateClick={handleDateClick}
eventAdd={(e) => {
console.log("eventAdd", e);
}}
eventChange={(e) => {
console.log("eventChange", e);
}}
eventRemove={(e) => {
console.log("eventRemove", e);
}}
/>
</Col>
</Row>
</Container>
<CustomModal
title={state.state === "update" ? "Update Booking" : "Make a Booking"}
isOpen={modal}
toggle={handleCloseModal}
onCancel={handleCloseModal}
onSubmit={state.clickInfo ? handleEdit : handleSubmit}
submitText={state.clickInfo ? "Update" : "Save"}
onDelete={state.clickInfo && handleDelete}
deleteText="Delete"
>
<FormGroup>
<Label for="#gmail.com">Trainer</Label>
<br/>
<select className="form-control" name="title"
value={title} onChange={(e) => setTitle(e.target.value)}>
<option></option>
<option></option>
<option></option>
<option></option>
</select>
</FormGroup>
<FormGroup>
<Label for="#gmail.com">Start Time - End Time</Label>
<DateRangePicker
initialSettings={{
locale: {
format: "M/DD hh:mm A"
},
startDate: start,
endDate: end,
timePicker: true
}}
onApply={(event, picker) => {
setStart(new Date(picker.startDate));
setEnd(new Date(picker.endDate));
}}
>
<input className="form-control" type="text" />
</DateRangePicker>
</FormGroup>
</CustomModal>
<CustomModal
title={state.state === "resize" ? "Resize Event" : "Drop Event"}
isOpen={confirmModal}
toggle={() => {
state.checkInfo.revert();
setConfirmModal(false);
}}
onCancel={() => {
state.checkInfo.revert();
setConfirmModal(false);
}}
cancelText="Cancel"
onSubmit={() => setConfirmModal(false)}
submitText={"OK"}
>
Do you want to {state.state} this event?
</CustomModal>
</div>
);
}
I tried to apply the code below to my code by applying calendar id and api key on Google, but I don't know.
let calendar = new Calendar(calendarEl, {
plugins: [ googleCalendarPlugin ],
googleCalendarApiKey: '<YOUR API KEY>',
events: {
googleCalendarId: 'abcd1234#group.calendar.google.com',
className: 'gcal-event' // an option!
}
});
My code is pretty straightforward but leads to the following warning in the console:
index.js:1 Warning: Duplicated key 'undefined' used in Menu by path []
This is so odd since all the relevant elements do have unique keys...?!
import { useState } from 'react';
import {
Layout,
Menu
} from 'antd';
import './navigation.less';
const { Sider } = Layout;
const items = [
{
id: 'abc',
title: "MenuItem 1",
navKey: 1,
disabled: false
},
{
id: 'def',
title: "MenuItem 2",
navKey: 2,
disabled: false
},
{
id: 'ghj',
title: "MenuItem 3",
navKey: 3,
disabled: true
},
]
const MenuItem = ({navKey, title, disabled}) => {
return (
<Menu.Item key={navKey} disabled={disabled}>
{title}
</Menu.Item>
)
}
const menu = items.map((item) => <MenuItem key={item.id} {...item} />);
const Navigation = () => {
const [collapsed, setCollapsed] = useState(false);
return (
<Sider collapsible collapsed={collapsed} onCollapse={() => setCollapsed(!collapsed)} width={260}>
<Menu theme="light" mode="inline">
<Menu.Divider />
{menu}
<Menu.Divider />
</Menu>
</Sider>
);
}
export default Navigation;
Your menu syntax is wrong.
The change below will get it to work.
const menu = items.map((item) => MenuItem(item))
Starter in React-hook project
I need to call openModal() from column.js which is defined in Table.js and need to fetch data and open the new modal form Table.js. Its react-hook project
column.js
// ** React Imports
import { Link } from 'react-router-dom'
// ** Custom Components
import Avatar from '#components/avatar'
// ** Store & Actions
import { getUser, deleteUser } from '../store/action'
import { store } from '#store/storeConfig/store'
import { useSelector } from 'react-redux'
// ** Third Party Components
import { Badge, UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'
import { Slack, User, Settings, Database, Edit2, MoreVertical, FileText, Trash2, Archive } from 'react-feather'
// ** Renders Client Columns
const renderClient = row => {
const stateNum = Math.floor(Math.random() * 6),
states = ['light-success', 'light-danger', 'light-warning', 'light-info', 'light-primary', 'light-secondary'],
color = states[stateNum]
if (row.user_avatar.length) {
const server_base_url = "http://localhost:3001/uploads/"
return <Avatar className='mr-1' img={`${server_base_url}${row.user_avatar}`} width='32' height='32' />
} else {
return <Avatar color={color || 'primary'} className='mr-1' content={row.user_fullname || 'John Doe'} initials />
}
}
const renderSerialNo = row => {
const param = useSelector(state => state.users.params)
const serial = ((param.page - 1) * param.perPage) + row + 1
return (serial)
}
// ** Renders Role Columns
const renderRole = row => {
const roleObj = {
subscriber: {
class: 'text-primary',
icon: User
},
maintainer: {
class: 'text-success',
icon: Database
},
editor: {
class: 'text-info',
icon: Edit2
},
author: {
class: 'text-warning',
icon: Settings
},
admin: {
class: 'text-danger',
icon: Slack
}
}
const Icon = roleObj[row.type_name] ? roleObj[row.type_name].icon : Edit2
return (
<span className='text-truncate text-capitalize align-middle'>
<Icon size={18} className={`${roleObj[row.type_name] ? roleObj[row.type_name].class : ''} mr-50`} />
{row.type_name}
</span>
)
}
const statusObj = {
2: 'light-warning',
0: 'light-success',
1: 'light-secondary'
}
export const columns = (openModal) => [
{
name: '#',
maxWidth: '3px',
selector: 'serial',
sortable: true,
cell: (row, index) => (renderSerialNo(index))
},
{
name: 'User',
minWidth: '300px',
selector: 'fullName',
sortable: true,
cell: row => (
<div className='d-flex justify-content-left align-items-center'>
{renderClient(row)}
<div className='d-flex flex-column'>
<Link
to={`/apps/user/view/${row.id}`}
className='user-name text-truncate mb-0'
onClick={() => store.dispatch(getUser(row.user_id))}
>
<span className='font-weight-bold'>{row.user_fullname}</span>
</Link>
<small className='text-truncate text-muted mb-0'>#{row.user_name}</small>
</div>
</div>
)
},
{
name: 'Email',
minWidth: '220px',
selector: 'email',
sortable: true,
cell: row => row.user_email
},
{
name: 'Role',
minWidth: '172px',
selector: 'role',
sortable: true,
cell: row => renderRole(row)
},
{
name: 'Status',
minWidth: '138px',
selector: 'status',
sortable: true,
cell: row => (
<Badge className='text-capitalize' color={statusObj[row.user_status]} pill>
{row.user_status_text}
</Badge>
)
},
{
name: 'Actions',
minWidth: '10px',
cell: row => (
<UncontrolledDropdown>
<DropdownToggle tag='div' className='btn btn-sm'>
<MoreVertical size={14} className='cursor-pointer' />
</DropdownToggle>
<DropdownMenu right>
<DropdownItem
tag={Link}
to={`/apps/user/view/${row.user_id}`}
className='w-100'
onClick={() => store.dispatch(getUser(row.user_id))}
>
<FileText size={14} className='mr-50' />
<span className='align-middle'>Details</span>
</DropdownItem>
<DropdownItem
tag={Link}
to={`/apps/user/edit/${row.user_id}`}
className='w-100'
onClick={() => store.dispatch(getUser(row.user_id))}
>
<Archive size={14} className='mr-50' />
<span className='align-middle'>Edit</span>
</DropdownItem>
<DropdownItem className='w-100' onClick={() => openModal(row)}>
<Trash2 size={14} className='mr-50' />
<span className='align-middle'>Delete</span>
</DropdownItem>
</DropdownMenu>
</UncontrolledDropdown>
)
}
]
Table.js
// ** React Imports
import { Fragment, useState, useEffect } from 'react'
// ** Invoice List Sidebar
import Sidebar from './Sidebar'
// ** Columns
import { columns } from './columns'
// ** Store & Actions
import { getAllData, getData } from '../store/action'
import { useDispatch, useSelector } from 'react-redux'
// ** Third Party Components
import Select from 'react-select'
import ReactPaginate from 'react-paginate'
import { ChevronDown } from 'react-feather'
import DataTable from 'react-data-table-component'
import { selectThemeColors } from '#utils'
import { Card, CardHeader, CardTitle, CardBody, Input, Row, Col, Label, CustomInput, Button } from 'reactstrap'
// ** Styles
import '#styles/react/libs/react-select/_react-select.scss'
import '#styles/react/libs/tables/react-dataTable-component.scss'
// ** Table Header
const CustomHeader = ({ toggleSidebar, handlePerPage, rowsPerPage, handleFilter, searchTerm }) => {
return (
<div className='invoice-list-table-header w-100 mr-1 ml-50 mt-2 mb-75'>
<Row>
<Col xl='6' className='d-flex align-items-center p-0'>
<div className='d-flex align-items-center w-100'>
<Label for='rows-per-page'>Show</Label>
<CustomInput
className='form-control mx-50'
type='select'
id='rows-per-page'
value={rowsPerPage}
onChange={handlePerPage}
style={{
width: '5rem',
padding: '0 0.8rem',
backgroundPosition: 'calc(100% - 3px) 11px, calc(100% - 20px) 13px, 100% 0'
}}
>
<option value='10'>10</option>
<option value='25'>25</option>
<option value='50'>50</option>
</CustomInput>
<Label for='rows-per-page'>Entries</Label>
</div>
</Col>
<Col
xl='6'
className='d-flex align-items-sm-center justify-content-lg-end justify-content-start flex-lg-nowrap flex-wrap flex-sm-row flex-column pr-lg-1 p-0 mt-lg-0 mt-1'
>
<div className='d-flex align-items-center mb-sm-0 mb-1 mr-1'>
<Label className='mb-0' for='search-invoice'>
Search:
</Label>
<Input
id='search-invoice'
className='ml-50 w-100'
type='text'
value={searchTerm}
onChange={e => handleFilter(e.target.value)}
/>
</div>
<Button.Ripple color='primary' onClick={toggleSidebar}>
Add New User
</Button.Ripple>
</Col>
</Row>
</div>
)
}
const UsersList = () => {
// ** Store Vars
const dispatch = useDispatch()
const store = useSelector(state => state.users)
// ** States
const [searchTerm, setSearchTerm] = useState('')
const [currentPage, setCurrentPage] = useState(1)
const [rowsPerPage, setRowsPerPage] = useState(10)
const [sidebarOpen, setSidebarOpen] = useState(false)
const [currentRole, setCurrentRole] = useState({ value: '', label: 'Select Role' })
const [currentPlan, setCurrentPlan] = useState({ value: '', label: 'Select Plan' })
const [currentStatus, setCurrentStatus] = useState({ value: '', label: 'Select Status', number: 0 })
// ** Function to toggle sidebar
const toggleSidebar = () => setSidebarOpen(!sidebarOpen)
// ** Get data on mount
useEffect(() => {
dispatch(getAllData())
dispatch(
getData({
page: currentPage,
perPage: rowsPerPage,
role: currentRole.value,
currentPlan: currentPlan.value,
status: currentStatus.value,
q: searchTerm
})
)
}, [dispatch, store.data.length])
// ** User filter options
const roleOptions = useSelector(state => state.users.roleOptions)
const statusOptions = [
{ value: '', label: 'Select Status', number: 0 },
{ value: '2', label: 'Pending', number: 1 },
{ value: '0', label: 'Active', number: 2 },
{ value: '1', label: 'Inactive', number: 3 }
]
// ** Function in get data on page change
const handlePagination = page => {
dispatch(
getData({
page: page.selected + 1,
perPage: rowsPerPage,
role: currentRole.value,
currentPlan: currentPlan.value,
status: currentStatus.value,
q: searchTerm
})
)
setCurrentPage(page.selected + 1)
}
// ** Function in get data on rows per page
const handlePerPage = e => {
const value = parseInt(e.currentTarget.value)
dispatch(
getData({
page: currentPage,
perPage: value,
role: currentRole.value,
currentPlan: currentPlan.value,
status: currentStatus.value,
q: searchTerm
})
)
setRowsPerPage(value)
}
// ** Function in get data on search query change
const handleFilter = val => {
setSearchTerm(val)
dispatch(
getData({
page: currentPage,
perPage: rowsPerPage,
role: currentRole.value,
currentPlan: currentPlan.value,
status: currentStatus.value,
q: val
})
)
}
// ** Custom Pagination
const CustomPagination = () => {
const count = Number(Math.ceil(store.total / rowsPerPage))
return (
<ReactPaginate
previousLabel={''}
nextLabel={''}
pageCount={count || 1}
activeClassName='active'
forcePage={currentPage !== 0 ? currentPage - 1 : 0}
onPageChange={page => handlePagination(page)}
pageClassName={'page-item'}
nextLinkClassName={'page-link'}
nextClassName={'page-item next'}
previousClassName={'page-item prev'}
previousLinkClassName={'page-link'}
pageLinkClassName={'page-link'}
containerClassName={'pagination react-paginate justify-content-end my-2 pr-1'}
/>
)
}
// ** Table data to render
const dataToRender = () => {
const filters = {
role: currentRole.value,
currentPlan: currentPlan.value,
status: currentStatus.value,
q: searchTerm
}
const isFiltered = Object.keys(filters).some(function (k) {
return filters[k].length > 0
})
if (store.data.length > 0) {
return store.data
} else if (store.data.length === 0 && isFiltered) {
return []
} else {
return store.allData.slice(0, rowsPerPage)
}
}
// ** Opening modal
const openModal= (row) => {
//Here i need to get the value
console.log(openModal)
}
return (
<Fragment>
<Card>
<CardHeader>
<CardTitle tag='h4'>User Lists</CardTitle>
</CardHeader>
<CardBody>
<Row>
<Col md='4'>
<Select
isClearable={false}
theme={selectThemeColors}
className='react-select'
classNamePrefix='select'
options={roleOptions}
value={currentRole}
onChange={data => {
setCurrentRole(data)
dispatch(
getData({
page: currentPage,
perPage: rowsPerPage,
role: data.value,
currentPlan: currentPlan.value,
status: currentStatus.value,
q: searchTerm
})
)
}}
/>
</Col>
<Col md='4'>
<Select
theme={selectThemeColors}
isClearable={false}
className='react-select'
classNamePrefix='select'
options={statusOptions}
value={currentStatus}
onChange={data => {
setCurrentStatus(data)
dispatch(
getData({
page: currentPage,
perPage: rowsPerPage,
role: currentRole.value,
currentPlan: currentPlan.value,
status: data.value,
q: searchTerm
})
)
}}
/>
</Col>
</Row>
</CardBody>
</Card>
<Card>
<DataTable
noHeader
pagination
subHeader
responsive
paginationServer
columns={columns(openModal)}
sortIcon={<ChevronDown />}
className='react-dataTable'
paginationComponent={CustomPagination}
data={dataToRender()}
subHeaderComponent={
<CustomHeader
toggleSidebar={toggleSidebar}
handlePerPage={handlePerPage}
rowsPerPage={rowsPerPage}
searchTerm={searchTerm}
handleFilter={handleFilter}
/>
}
/>
</Card>
<Sidebar open={sidebarOpen} toggleSidebar={toggleSidebar} />
</Fragment>
)
}
export default UsersList
I'm trying to implement this in react-hook project. Need to get resolved to open the modal and data need to be showed in modal.Tried everything and need to call useState() for define modal but in column.js its showing its violation of hooks components also its just need to export the column so need to get the openModal() event trgger in Table.js
Your code will look like this:
**column.js**
// ** React Imports
import { Link } from 'react-router-dom'
// ** Custom Components
import Avatar from '#components/avatar'
// ** Store & Actions
import { getUser, deleteUser } from '../store/action'
import { store } from '#store/storeConfig/store'
import { useSelector } from 'react-redux'
// ** Third Party Components
import { Badge, UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'
import { Slack, User, Settings, Database, Edit2, MoreVertical, FileText, Trash2, Archive } from 'react-feather'
export const columns = (openModal) => [
{
name: '#',
maxWidth: '3px',
selector: 'serial',
sortable: true,
cell: (row, index) => (renderSerialNo(index))
},
{
name: 'User',
minWidth: '138px',
selector: 'user',
sortable: true,
cell: row => (
<Badge className='text-capitalize' color={statusObj[row.user_status]} pill>
{row.user_status_text}
</Badge>
)
},
{
name: 'Actions',
minWidth: '10px',
cell: row => (
<UncontrolledDropdown>
<DropdownItem className='w-100' onClick={() => openModal(row)}>
<Trash2 size={14} className='mr-50' />
<span className='align-middle'>Delete</span>
</DropdownItem>
</DropdownMenu>
</UncontrolledDropdown>
)
}
]```
**Table.js**
```// ** React Imports
import { Fragment, useState, useEffect } from 'react'
// ** Columns
import { columns } from './columns'
// ** Store & Actions
import { getAllData, getData } from '../store/action'
import { useDispatch, useSelector } from 'react-redux'
// ** Third Party Components
import Select from 'react-select'
import ReactPaginate from 'react-paginate'
import { ChevronDown } from 'react-feather'
import DataTable from 'react-data-table-component'
import { selectThemeColors } from '#utils'
import { Card, CardHeader, CardTitle, CardBody, Input, Row, Col, Label, CustomInput, Button } from 'reactstrap'
// ** Styles
import '#styles/react/libs/react-select/_react-select.scss'
import '#styles/react/libs/tables/react-dataTable-component.scss'
const UsersList = () => {
// ** Store Vars
const dispatch = useDispatch()
const store = useSelector(state => state.users)
// ** Get data on mount
useEffect(() => {
dispatch(getAllData())
dispatch(
getData()
)
}, [dispatch, store.data.length])
// ** User filter options
const roleOptions = useSelector(state => state.users.roleOptions)
const openModal= e => {
//need to get the value here
console.log(e)
}
...
return (
<Fragment>
<Card>
<DataTable
responsive
paginationServer
columns={columns(openModal)}
sortIcon={<ChevronDown />}
className='react-dataTable'
paginationComponent={CustomPagination}
data={dataToRender()}
}
/>
</Card>
<Sidebar open={sidebarOpen} toggleSidebar={toggleSidebar} />
</Fragment>
)
}
export default UsersList
I am trying to implement a context menu that should be displayed while the user is typing into a textfield using the MaterialUI context menu. The idea is to display suggestions in the context menu based on the currently typed in word in the textfield. So the context menu is being shown next to the caret input and should not disturb the user from continue typing.
But the problem is when the menu is displayed the focus is taken from the textfield and the user is unable to continue typing until the menu has been closed.
Is there a way to allow the menu to be shown and still keep the focus on the textfield allowing the user to continue typing?
<Menu
keepMounted
open={contextMenuOpen}
onClose={handleClose}
anchorReference="anchorPosition"
anchorPosition={
{ top: caretPositionY, left: caretPositionX}
}
>
<MenuItem onClick={handleClose}>Suggestion1</MenuItem>
<MenuItem onClick={handleClose}>Suggestion2</MenuItem>
<MenuItem onClick={handleClose}>Suggestion3</MenuItem>
<MenuItem onClick={handleClose}>Suggestion4</MenuItem>
</Menu>
If you can't make it work with Material UI Menu, the here is the alternative:-
Demo.js (using simple ul element with package classnames):-
import React, { useState, useEffect } from "react";
import classNames from "classnames";
import { makeStyles } from "#material-ui/core/styles";
import { Button, TextField } from "#material-ui/core";
import "./style.css";
const Demo = () => {
const classes = useStyles();
const [search, setSearch] = useState("");
const [openMenu, setOpenMenu] = useState(false);
const [menuItems, setMenuItems] = useState([
{ id: 1, name: "Profile" },
{ id: 2, name: "My Account" },
{ id: 3, name: "Logout" }
]);
const [menuItemsFiltered, setMenuItemsFiltered] = useState(menuItems);
const handleClose = () => {
setOpenMenu(false);
};
const handleSearch = value => {
if (value === "") {
setOpenMenu(false);
setMenuItemsFiltered(menuItems);
} else {
setOpenMenu(true);
setMenuItemsFiltered(() =>
menuItems.filter(
item => item.name.toLowerCase().indexOf(value.toLowerCase()) > -1
)
);
}
};
useEffect(() => {
handleSearch(search);
}, [search]);
return (
<div className={classes.root}>
<TextField
type="search"
value={search}
onChange={e => setSearch(e.target.value)}
/>
<div>
<Button
aria-controls="simple-menu"
aria-haspopup="true"
onClick={() => setOpenMenu(!openMenu)}
>
Open Menu
</Button>
<ul
className={
openMenu ? classes.menu : classNames(classes.menu, classes.menuHide)
}
>
{menuItemsFiltered.map(item => (
<li
key={item.id}
className={classes.menuItem}
onClick={() => handleClose()}
>
{item.name}
</li>
))}
</ul>
</div>
</div>
);
};
export default Demo;
const useStyles = makeStyles(theme => ({
root: {
display: "flex"
},
menu: {
listStyle: "none",
padding: "0.5rem",
backgroundColor: theme.palette.grey[100],
"& li:not(:last-child)": {
marginBottom: "1rem"
}
},
menuHide: {
display: "none"
},
menuItem: {
"&:hover": {
cursor: "pointer",
color: theme.palette.primary.main
}
}
}));
You can see the working demo here in sandbox.
Update 2022
https://codesandbox.io/s/react-insert-context-menu-value-into-textarea-rhq5wq?file=/src/App.tsx
Addtional feature is setting cursor after the pasted value
import { Box, Menu, MenuItem, TextField, Typography } from "#mui/material";
import React, { useLayoutEffect } from "react";
import { useRef, useState } from "react";
export default function App() {
const [value, setValue] = useState(
"Lorem ipsum, dolor sit amet consectetur adipisicing elit. Accusamus, nemo. Doloremque eligendi aliquam repellendus reiciendis doloribus excepturi asperiores quaerat corporis."
);
const [selectionEnd, setSelectionEnd] = useState(0);
const textareaRef = useRef();
const [contextMenu, setContextMenu] = React.useState<{
mouseX: number;
mouseY: number;
} | null>(null);
const handleContextMenu = (event: React.MouseEvent) => {
event.preventDefault();
setContextMenu(
contextMenu === null
? {
mouseX: event.clientX + 2,
mouseY: event.clientY - 6
}
: // repeated contextmenu when it is already open closes it with Chrome 84 on Ubuntu
// Other native context menus might behave different.
// With this behavior we prevent contextmenu from the backdrop to re-locale existing context menus.
null
);
};
const handleClose = () => {
setContextMenu(null);
};
const insertText = (text) => () => {
const selectionStart = textareaRef.current.selectionStart;
const selectionEnd = textareaRef.current.selectionEnd;
const insertText = ` {{${text}}} `;
setSelectionEnd(selectionEnd + insertText.length);
const newValue = `${value.substring(
0,
selectionStart
)}${insertText}${value.substring(selectionEnd, value.length)}`;
setValue(newValue);
handleClose();
};
useLayoutEffect(() => {
//Sets the cursor at the end of inserted text
textareaRef.current.selectionEnd = selectionEnd;
}, [selectionEnd]);
return (
<Box onContextMenu={handleContextMenu}>
<Typography mb={2}>Right click on textarea to add some text</Typography>
<TextField
label="Textarea"
multiline
minRows={10}
inputRef={textareaRef}
value={value}
onChange={({ target }) => setValue(target.value)}
fullWidth
/>
<Menu
open={contextMenu !== null}
onClose={handleClose}
anchorReference="anchorPosition"
anchorPosition={
contextMenu !== null
? { top: contextMenu.mouseY, left: contextMenu.mouseX }
: undefined
}
>
<MenuItem onClick={insertText("VarA")}>VarA</MenuItem>
<MenuItem onClick={insertText("VarB")}>VarB</MenuItem>
</Menu>
</Box>
);
}
I have a Select and the inputs are in Chip Format. I tried console log of the value selected and it is getting it fine. But for some reason, it does not get displayed on the select box. What am I doing wrong here?
handleChange = event => {
this.setState({ badge : event.target.value });
};
const chipOptions = [
{key: 1, 'text': 'text1', 'value': 'text1'},
{key: 2, 'text':'text2', 'value':'text2'},
{key: 3, 'text':'text3', 'value':'text3'}
]
<FormControl className={classes.formControl}>
<Select
value={this.state.badge}
onChange={this.handleChange}
inputProps={{
name: 'badge',
id: 'badge-simple',
}}
>
{chipOptions && chipOptions.map(option=> (
<Chip key={option.value} label={option.value} className={classes.chip} value={option.value} />
))}
</Select>
</FormControl>
The default manner in which Select renders the selected value is to render its children. In the Select source code as it is looping through the children of the Select, it does the following:
selected = areEqualValues(value, child.props.value);
if (selected && computeDisplay) {
displaySingle = child.props.children;
}
This is based on the assumption of the Select having MenuItem children. For instance, in the following example the first MenuItem would be selected and that MenuItem's children would be the text "Item 1":
<Select value={1}>
<MenuItem value={1}>Item 1</MenuItem>
<MenuItem value={2}>Item 2</MenuItem>
</Select>
Your Chips don't have children, so nothing is displayed. You can customize this behavior by specifying the renderValue property on Select. This is a function that receives the value and can decide what to render.
The following example shows using the renderValue prop to render a Chip:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import FormControl from "#material-ui/core/FormControl";
import Chip from "#material-ui/core/Chip";
import Select from "#material-ui/core/Select";
import { withStyles } from "#material-ui/core/styles";
const styles = theme => ({
formControl: {
margin: theme.spacing.unit,
minWidth: 120
}
});
const chipOptions = [
{ key: 1, text: "text1", value: "text1" },
{ key: 2, text: "text2", value: "text2" },
{ key: 3, text: "text3", value: "text3" }
];
function App({ classes }) {
const [value, setValue] = useState("text1");
const renderChip = value => {
return <Chip label={value} className={classes.chip} />;
};
return (
<>
<FormControl className={classes.formControl}>
<Select
inputProps={{
name: "badge",
id: "badge-simple"
}}
renderValue={renderChip}
value={value}
onChange={event => {
console.log(event.target.value);
setValue(event.target.value);
}}
>
{chipOptions &&
chipOptions.map(option => (
<Chip
key={option.value}
label={option.value}
className={classes.chip}
value={option.value}
/>
))}
</Select>
</FormControl>
</>
);
}
const StyledApp = withStyles(styles)(App);
const rootElement = document.getElementById("root");
ReactDOM.render(<StyledApp />, rootElement);