I am using Reactjs in my project. NavLink active property does not work with nested pages inside the same tab?? It works just in the main tab anything inside the main tab does not work with. I want the white border appear in the main tab while open any pages inside it.
import { NavLink } from "react-router-dom";
function Navbar() {
let activeStyle = {
border: "solid 3px #FFF",
};
return (
<>
<nav className="navbar">
<ul className="nav-items">
{navItems.map((item) => {
if (item.id === "3") {
return (
<li
key={item.id}
className={item.cName}
onMouseClick={() => setDropdown(true)}
onMouseEnter={() => setDropdown(true)}
onMouseLeave={() => setDropdown(false)}
>
<NavLink to={item.title}
style={({ isActive }) =>
isActive ? activeStyle: undefined
}
>{item.title}</NavLink>
{dropdown && <Dropdown />}
</li>
);
}
return (
<li key={item.id}
className={item.cName}>
<NavLink to={item.path}
style={({ isActive }) =>
isActive ? activeStyle: undefined
}>{item.title}</NavLink>
</li>
);
})}
</ul>
</nav>
</>
);
}
export default Navbar;
Related
I am trying to style the ListItem on hover, but the problem is that the list is being mapped over to create multiple list items. When I change the hover style it changes the style for all list items when being hovered. How do I target just one element? Below is the code.
I am trying to style the prop.icon and ListItemText when they are hovered.
Sidebar.js
var links = (
<List className={classes.list}>
{routes.map((prop, key) => {
if (prop.path === "/login") {
return;
}
return (
<NavLink
to={prop.layout + prop.path}
className={classes.item}
activeClassName="active"
key={key}
>
<ListItem button className={classes.itemLink} onMouseEnter={MouseEnter} onMouseLeave={MouseLeave}>
<prop.icon
className={classNames(classes.itemIcon)}
/>
<ListItemText
primary={prop.name}
className={classNames(classes.itemText)}
disableTypography={true}
/>
</ListItem>
</NavLink>
);
})}
</List>
);
MouseEnter & MouseLeave
const MouseEnter = (e) => {
setHovered(true);
}
const MouseLeave = (e) => {
setHovered(false);
}
With CSS it would be as simple as that:
.list-item:hover{
background-color: purple;
color: white;
}
/* just for decoration, you don't need the code below*/
ul{
list-style-type: none;
}
li{
padding: 1rem;
cursor: pointer;
border: solid 1px black;
margin: 0.5rem;
}
<script src="https://kit.fontawesome.com/a076d05399.js"></script>
<ul >
<li class="list-item" >
<span >♠ </span>spades
</li>
<li class="list-item">
<span >♣ </span>clubs
</li>
<li class="list-item">
<span >♦ </span>dimonds
</li>
</ul>
or with your code:
//links.css
.list-item:hover{
background-color:red;
}
//links.js
import "./links.css"
var links = (
<List className={classes.list}>
{routes.map((prop, key) => {
if (prop.path === "/login") {
return;
}
return (
<NavLink
to={prop.layout + prop.path}
className={classes.item}
activeClassName="active"
key={key}
>
<ListItem button className={classes.itemLink + " list-item"} onMouseEnter={MouseEnter} onMouseLeave={MouseLeave}>
<prop.icon
className={classNames(classes.itemIcon)}
/>
<ListItemText
primary={prop.name}
className={classNames(classes.itemText)}
disableTypography={true}
/>
</ListItem>
</NavLink>
);
})}
</List>
);
Using the active index in the array worked for me besides using boolean values.
sidebar.js
const [hovered, setHovered] = useState(-1);
const MouseEnter = (index) => {
setHovered(index);
}
const MouseLeave = () => {
setHovered(-1);
}
var links = (
<List className={classes.list}>
{routes.map((prop, index, key ) => {
if (prop.path === "/login") {
return;
}
return (
<NavLink
to={prop.layout + prop.path}
className={classes.item}
activeClassName="active"
key={key}
>
<ListItem button className={hovered === index ? 'hoverLinear' : classes.itemContainer} onMouseEnter={() => MouseEnter(index)} onMouseLeave={MouseLeave}>
<prop.icon
className={classNames(classes.itemIcon)}
style={hovered === index ? {color: 'white'} : {color: "#8B2CF5"}}
/>
<ListItemText
primary={prop.name}
className={classNames(classes.itemText, ' mx-10')}
style={hovered === index ? {color: 'white'} : {color: "#3A448E"}}
disableTypography={true}
/>
</ListItem>
</NavLink>
);
})}
</List>
);
I have header component, where I want to toggle className between all the elements of menu (if one of the elements of menu is active and user is clicking to another element - this element become active and all others no). I have a code like this
import React, { useState } from 'react';
import './header.scss';
export const Header = ({ favoriteCount }) => {
const [activeIndex, setActiveIndex] = useState(0);
function toggleClass(index) {
setActiveIndex(index);
}
return (
<header className="header">
<div className="container header-container">
<ul className="header-menu">
<li>
<a
className={
activeIndex === 0
? 'header-menu__link active'
: 'header-menu__link'
}
onClick={() => {
toggleClass(0);
}}
href="##"
>
Characters
</a>
</li>
<li>
<a
className={
activeIndex === 1
? 'header-menu__link active'
: 'header-menu__link'
}
onClick={() => {
toggleClass(0);
}}
href="##"
>
Favorites
</a>
</li>
</ul>
<div className="header-favorite-count">
<i className="far fa-heart"></i>
{favoriteCount}
</div>
</div>
</header>
);
};
and styles to visualise toggling classes
&-menu__link {
color: lightgray;
}
.active {
color: #fff;
}
This approach is working but looks creepy. Maybe somebody knows how to optimize it?
I wouldn't use the index, I'd use the text of the item. I'd also include that text in the href so that there's an indication of what the anchor leads to. To avoid repeated code, you might put the menu items in a reusable array, something like this:
const menuItems = [
"Characters",
"Favorites",
];
export const Header = ({ favoriteCount }) => {
const [activeItem, setActiveItem] = useState("");
const setActiveItem = useCallback((event) => {
setActiveItem(event.currentTarget.href.substring(2));
}, []);
const list = menuItems.map(item =>
<li key={item}>
<a
className={`header-menu__link ${item === activeItem ? "active" : ""}`}
onClick={setActiveItem}
href={"##" + item}>
{item}
</a>
</li>
);
return (
<header className="header">
<div className="container header-container">
<ul className="header-menu">
{list}}
</ul>
<div className="header-favorite-count">
<i className="far fa-heart"></i>
{favoriteCount}
</div>
</div>
</header>
);
};
Here I have an image of the current header render. The headercomprises of a HeaderMenu and 3 Links. The links are working fine, but the HeaderMenu is causing the links to fall below it. HeaderMenu contains a div which wraps a Button and Popper, acting as a dropdown menu.
I'm also not sure if this design structure is correct, but for the styles, I have one styles.js file which I pull from. I then pass these styles down as props to the smaller components for rendering. That's why the components here have a props classes that comes from useStyles in index.js.
Header.js
import { AppBar, Button, Link, Toolbar, Typography } from '#material-ui/core'
import HeaderMenu from './HeaderMenu.js'
const Header = (props) => {
const { classes } = props
return (
<AppBar position="static" color="default" elevation={0} className={classes.appBar}>
<Toolbar className={classes.toolbar}>
<Typography variant="h6" color="inherit" noWrap className={classes.toolbarTitle}>
Company name
</Typography>
<nav>
<HeaderMenu classes={classes}/>
<Link variant="button" color="textPrimary" href="#" className={classes.link}>
Here
</Link>
<Link variant="button" color="textPrimary" href="#" className={classes.link}>
Enterprise
</Link>
<Link variant="button" color="textPrimary" href="#" className={classes.link}>
Support
</Link>
</nav>
<Button href="#" color="primary" variant="outlined" className={classes.link}>
Login
</Button>
</Toolbar>
</AppBar>
)
}
export default Header
HeaderMenu.js
import React from 'react'
import { Button, ClickAwayListener, Grow, Paper, Popper, MenuItem, MenuList } from '#material-ui/core'
const HeaderMenu = (props) => {
const { classes } = props
const [open, setOpen] = React.useState(false)
const anchorRef = React.useRef(null)
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen)
}
const handleClose = (event) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
return
}
setOpen(false)
}
function handleListKeyDown (event) {
if (event.key === 'Tab') {
event.preventDefault()
setOpen(false)
}
}
// return focus to the button when we transitioned from !open -> open
const prevOpen = React.useRef(open)
React.useEffect(() => {
if (prevOpen.current === true && open === false) {
anchorRef.current.focus()
}
prevOpen.current = open
}, [open])
return (
<div className={classes.link}>
<Button
ref={anchorRef}
aria-controls={open ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={handleToggle}
>
Details
</Button>
<Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{ transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom' }}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList autoFocusItem={open} id="menu-list-grow" onKeyDown={handleListKeyDown}>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</div>
)
}
export default HeaderMenu
You should wrap all your links into a div and then add this to your <nav></nav>
display: flex;
justify-content: space-evenly;
align-items: center;
This will allow it to stay everything in the same line.
Is there any method for map function to only execute Dropdown menu for one cart.For now if i click on one Menu icon , every dropdown appears for every Card in map.
const [isOpen, setIsOpen] = useState(false);
{albums.map((album, i) => (
<Card width="23rem" height="16rem" bckImg={album.bckImgUrl} key={i}>
<AlbumtTitle color={album.color}>{album.name}</AlbumtTitle>
<LinkButton background={album.color} to={`/albums/${album._id}`}>
See more
</LinkButton>
<KebabMenu onClick={() => setIsOpen(!isOpen)} open={isOpen}>
{isOpen ? (
<Dropdown
ref={ref}
style={{
width: "50px",
height: "50px",
}}
>
<li>
<Link to={`editAlbum/${album._id}`}>Edit</Link>
</li>
<hr />
<li>Delete</li>
</Dropdown>
) : (
<Dots />
)}
</KebabMenu>
</Card>
))}
Your isOpen state can be an object that tracks the open state per item in the album:
const [isOpen, setIsOpen] = useState({});
{albums.map((album, i) => (
<Card width="23rem" height="16rem" bckImg={album.bckImgUrl} key={i}>
<AlbumtTitle color={album.color}>{album.name}</AlbumtTitle>
<LinkButton background={album.color} to={`/albums/${album._id}`}>
See more
</LinkButton>
<KebabMenu
onClick={() =>
setIsOpen({ ...isOpen, [album._id]: !isOpen[album._id] })
}
open={!!isOpen[album._id]}
>
{isOpen[album._id] ? (
<Dropdown
ref={ref}
style={{
width: "50px",
height: "50px",
}}
>
<li>
<Link to={`editAlbum/${album._id}`}>Edit</Link>
</li>
<hr />
<li>Delete</li>
</Dropdown>
) : (
<Dots />
)}
</KebabMenu>
</Card>
))}
Instead of
{albums.map((album, i) => (...your code...))}
you need to check whether you have at least one item in albums, and if yes, process it:
{if (albums.length > 0) {
return albums.slice(0,1).map((album, i) => (...your code...))
} else {
return null // or any other actions you want to take in case there are no albums
}
}
To clarify, we check the length of the albums, if it is not empty. If it is, we take only the first element and process it.
I am wanting to hide other sibling divs (dropdowns in my case) when I click the statusPillDropdown
so far I click I am setting the status to true and opening the div,
{DropDown ${toggleStatusDropdown ? "open": ""}}
Do I just need to set the state to false for previously opened ones? Not sure how to do this.
thank you
import React, { useState } from "react";
import "./StatusPillDropdown.scss";
function StatusPillDropdown({
cellData,
rowItemId,
onStatusPillDropdownChange
}) {
const [toggleStatusDropdown, setToggleStatusDropdown] = useState();
const toggleDropdown = (action, rowItemId, e) => {
if (action === "pillClick") {
setToggleStatusDropdown(true);
} else {
onStatusPillDropdownChange(rowItemId, e.target.getAttribute("value"));
setToggleStatusDropdown(false);
}
};
const renderstatusPillDropdown = (cellData, rowItemId) => (
<React.Fragment>
<span
className="statusPillDropdown"
onClick={() => toggleDropdown("pillClick", rowItemId)}
>
<span className={`status-pill ${cellData.type}`}>{cellData.text}</span>
</span>
<div className="status">
<div className="dropdown-container">
<div className={`DropDown ${toggleStatusDropdown ? "open" : ""}`}>
<ul>
<li
value="Information only"
onClick={e => toggleDropdown("liClick", rowItemId, e)}
>
<span></span>Information only
</li>
<li
value="Unresolved"
onClick={e => toggleDropdown("liClick", rowItemId, e)}
>
<span className="unresolved"></span>Unresolved
</li>
<li
value="Partially Resolved"
onClick={e => toggleDropdown("liClick", rowItemId, e)}
>
<span className="partyResolved"></span>Partially Resolved
</li>
<li
value="Fully Resolved"
onClick={e => toggleDropdown("liClick", rowItemId, e)}
>
<span className="resolved"></span>Fully Resolved
</li>
</ul>
</div>
</div>
</div>
</React.Fragment>
);
return (
<React.Fragment>
{renderstatusPillDropdown(cellData, rowItemId)}
</React.Fragment>
);
}
export default StatusPillDropdown;