How to make Mantine burger close after link is selected? - javascript

I want to make the Mantine burger auto close after a nav choice is selected in the modal. It seems as if it is not possible for the Mantine burger to automatically close (because the x icon stays). I am using NextJS with Mantine & react-icons.
export default function HeaderMiddle({ links }: HeaderMiddleProps) {
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
const dark = colorScheme === 'dark';
const [opened, { toggle, open, close }] = useDisclosure(false, {
onOpen: () => burgerClick(),
onClose: () => closeAllModals(),
});
const [active, setActive] = useState(links[0].link);
const { classes, cx } = useStyles();
const items = links.map((link) => (
<a
key={link.label}
href={link.link}
className={cx(classes.link, {
[classes.linkActive]: active === link.link,
})}
onClick={(event) => {
event.preventDefault();
setActive(link.link);
}}
>
{link.label}
</a>
));
const burgerItems = links.map((link) => (
<a
key={link.label}
href={link.link}
className={cx(classes.link, {
[classes.linkActive]: active === link.link,
})}
onClick={(event) => {
event.preventDefault();
setActive(link.link);
closeAllModals();
}}
>
{link.label}
</a>
));
const burgerClick = () => {
openModal({
title: 'Subscribe to newsletter',
children: <>{burgerItems}</>,
});
};
return (
<Header height={56} mb={120}>
<Container className={classes.inner}>
<Burger
opened={opened}
onClick={toggle}
size="sm"
className={classes.burger}
/>
<Group className={classes.links} spacing={5}>
{items}
</Group>
{/* <MantineLogo size={28} /> */}
<Group spacing={0} className={classes.social} position="right" noWrap>
...
</Group>
</Container>
</Header>
);
}
Any help with this would be appreciated.
next: 12.1.5
#mantine/core: 4.2.12

After clicking the link call close method of useDisclosure, it will make opened to false. Burger state is depend on opened so, it wil close the burger on false.
const burgerItems = links.map((link) => (
<a
key={link.label}
href={link.link}
className={cx(classes.link, {
[classes.linkActive]: active === link.link,
})}
onClick={(event) => {
event.preventDefault();
setActive(link.link);
closeAllModals();
// It will set opened to false (so it will close Burger)
close()
}}
>
{link.label}
</a>
));

I found something that might help you
On the Mantine Doc, one header called 'Responsive header' is working fine after you clicked on any of the burger items.
Simply follow the codes and should be fine!

Related

How to Close Floating Menu when clicking anywhere else but the menu? React JS

I was struck while creating a Floating menu in react JS, the task required is to close the floating menu when a click is recorded anywhere outside the menu, the code snippet is as follows
class SectionMenu extends Component {
state = {
isOpen: false,
activeSection: '',
sectionIds: [],
coordinates: []
}
render() {
const { sections, hideMenu } = this.props;
const { sectionIds } = this.state;
return (
<React.Fragment>
<FloatingMenu
id="floating-menu"
slideSpeed={10}
direction="left"
isOpen={this.state.isOpen}
spacing={16}
style={hideMenu ? {display: "none"} : null}
>
<MainButton
iconResting={}
iconActive={}
onClick={() => this.setState({isOpen:
!this.state.isOpen})}
size={}
/>
{"Random Code to bring up each menu item"}
</FloatingMenu>
</React.Fragment>
)
}
}
I am struck on how to create a useRef/useEffect hook function, is there any method to to it done without useRef as well?
if not how do I do it with useRef/useEffect and/or any other hook?
Try to refactor your code using functional component:
const SectionMenu = ({ sections, hideMenu }) => {
const [sectionsIds, setSectionsIds] = useState([]);
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
// whatever effect
}, [])
return (
<React.Fragment>
<FloatingMenu
id="floating-menu"
slideSpeed={10}
direction="left"
isOpen={isOpen}
spacing={16}
style={hideMenu ? {display: "none"} : null}
>
<MainButton
onClick={() => setIsOpen((prev) => !prev)}
/>
{"Random Code to bring up each menu item"}
</FloatingMenu>
</React.Fragment>
)
}
Yes, I've ran up into the same issue and this worked for me . You can change the onClick() as follows
let isClicked=true;
const {/*function name here*/} =()=>{
if(isClicked){
document.querySelector("{/*classname here*/}").style = "display:block "
isClicked=false;
}
else{
document.querySelector("{/*classname here*/}").style = "display:none"
isClicked=true;
}
}
This will toggle between true and false.

I need to open and close accordion based on arrow click

I am using Material UI accordion my issue is if I click on the arrow accordion will get open but again I click on the arrow it will not get closed I need to set it when the user clicks on the arrow according will close and open based on the arrow click check code sandbox link for better understanding.
export default function ControlledAccordions() {
const [expanded, setExpanded] = React.useState(false);
// const handleChange = (panel) => (event, isExpanded) => {
// setExpanded(isExpanded ? panel : false);
// };
const handleChange = (pannel) => {
setExpanded(pannel);
};
const panaalData = ["panel1", "panel2", "panel3", "panel4"];
return (
<div>
{panaalData.map((value, i) => {
return (
<Accordion expanded={expanded === `panel${i}`}>
<AccordionSummary
expandIcon={
<ExpandMoreIcon
onClick={() => {
handleChange(`panel${i}`);
}}
style={{ cursor: "pointer" }}
/>
}
aria-controls="panel1d-content"
id="panel1d-header"
>
fdsfdsf
</AccordionSummary>
<AccordionDetails>dfdf</AccordionDetails>
</Accordion>
);
})}
</div>
);
}
Code SandBox Link
you need to reset panel in that case. You can do that in change handler.
const handleChange = (pannel) => {
setExpanded(expended === pannel ? '' : pannel);
};
when you click the already expanded panel, it just sets it to be expanded again.
you need to check whether the clicked panel is already expanded and if so collapse it instead of expanding it:
const handleChange = (pannel) => {
if (expanded === pannel) setExpanded(false);
else setExpanded(pannel);
};
Create another component called MyAccordian and keep toggling accordion logic in that component. That way you don't need to handle toggling for each and every component separately.
export default function ControlledAccordions() {
const panaalData = ["panel1", "panel2", "panel3", "panel4"];
return (
<div>
{panaalData.map((value, i) => {
return <MyAccordian value={value} />;
})}
</div>
);
}
const MyAccordian = ({ value }) => {
const [expanded, setExpanded] = React.useState(false);
return (
<Accordion expanded={expanded}>
<AccordionSummary
expandIcon={
<ExpandMoreIcon
onClick={() => {
setExpanded((prev) => !prev);
}}
style={{ cursor: "pointer" }}
/>
}
aria-controls="panel1d-content"
id="panel1d-header"
>
{value}
</AccordionSummary>
<AccordionDetails>{value}</AccordionDetails>
</Accordion>
);
};
Working Demo
export default function ControlledAccordions() {
// initial state, everything is closed,
const [expandedIndex, setExpandedIndex] = React.useState(-1);
// this should be handleClic
const handleChange = (index) => {
// in useState, current expandedIndex is passed as the argument
// whatever we return will be set as the expandedIndex
setExpandedIndex((currentIndex) => {
// if any box is open, currentIndex will be that index
// when I click on the open box, it will set the expandedIndex=-1
if (currentIndex === index) {
return -1;
} else {
// If I reached here, that means I am on a closed box
// when I click I swithc the expandedIndex to current box's index
return index;
}
});
};
const panaalData = ["panel1", "panel2", "panel3", "panel4"];
return (
<div>
{panaalData.map((value, i) => {
// when handleChange runs on AccordionSummary expandedIndex===i
// that means when i click on the current box, it will be open
const isExpanded = expandedIndex === i;
return (
<Accordion expanded={isExpanded}>
<AccordionSummary
onClick={() => handleChange(i)}
expandIcon={
// I dont know #mui/material too much.
// main question is "I need to open and close accordion based on arrow click"
<ExpandMoreIcon
onClick={() => handleChange(i)}
style={{ cursor: "pointer" }}
/>
}
aria-controls="panel1d-content"
id="panel1d-header"
>
{value}
</AccordionSummary>
<AccordionDetails
style={{ backgroundColor: "green" }}
>{`box index ${i} is open`}</AccordionDetails>
</Accordion>
);
})}
</div>
);
}
proof of work:
const handleChange = (pannel) => {
setExpanded(!pannel);
};

Close Hamburger Menu When mobile menu Link is clicked

when I click the menu item the page loads but the menu stays open.
this is the Mobile Menu it JSX:
const MobileMenu = () => {
const navigation = [
{ link: '/applications', text: 'Applications' },
{ link: '/multi-media', text: 'Media' },
{ link: '/websites', text: 'Websites' },
{ link: '/mobile-apps', text: 'Mobile' },
{ link: '/support', text: 'Support' },
{ link: '/contact', text: 'Contact' },
{ link: '/', text: 'Login' },
];
return (
<NavMenuContainer>
<NavList>
<div className="flex pb-4 lg:px-6 lg:hidden">
<Searchbar id="mobile-search" />
</div>
{navigation.map(nav => (
<NavLink key={nav.text}>
<Link href={nav.link}><a>
{nav.text}
</a></Link>
</NavLink>
))}
</NavList>
</NavMenuContainer>
);
};
export default MobileMenu
this is NAV the MobileMenu menu is in(JSX:
export default function HamburgerMenu(props) {
const [isOpen, setOpen] = useState(false);
//change
const toggleMenu = () => {
let dd = document.body;
dd.classList.toggle("navbar-mobile");
setOpen(!isOpen);
};
return (
<Theburger>
<HamburgerMenuContainer>
<MenuToggle toggle={toggleMenu} isOpen={isOpen} />
<MenuContainer
initial={false}
animate={isOpen ? "open" : "closed"}
variants={menuVariants}
transition={menuTransition}
>
<ContentContainer>
<MobileMenu isOpen={isOpen} />
</ContentContainer>
</MenuContainer>
</HamburgerMenuContainer>
</Theburger>
);
}
this is the website main menu it TSX:
const Navbar: FC<NavbarProps> = ({ links }) => (
<NavbarRoot>
<Container>
<div className={s.nav}>
<div className="flex items-center">
<Link href="/"><a>
<div className="logo">
<Image width={106} height={27} src="/logo.svg" alt="brand" />
</div>
</a></Link>
<nav className={s.navMenu}>
{[...links3 ].map((page) => (
<span key={page.url}>
<Link href={page.url!}>
<a className={s.link}>
{page.name}
</a>
</Link>
</span>
))}
{links?.map((l) => (
<Link href={l.href} key={l.href}>
<a className={s.link}>{l.label}</a>
</Link>
))}
</nav>
</div>
<div className="flex items-center justify-end flex-1 space-x-8 mr-5">
<UserNav />
</div>
<div className="flex items-center justify-end flex-2">
<Nav />
</div>
</div>
</Container>
</NavbarRoot>
)
export default Navbar
Its a nextjs app Im using a Layout component in _app.tsx not sure if that matters but it really shouldn't, I Missed tsx with jsx and according to the docs at NextJS and javascript in general mixing them shouldn't cause problems.
You're missing to give the state as a prop to your toggle menu function.
Thus isOpen is always false and the state gets changed always to true.
Change your toggleMenu() to toggleMenu(isOpen) and it's fine.
export default function HamburgerMenu(props) {
const [isOpen, setOpen] = useState(false);
//change
const toggleMenu = (myState) => {
let dd = document.body;
dd.classList.toggle("navbar-mobile");
setOpen(!isOpen);
};
return (
<Theburger>
<HamburgerMenuContainer>
<MenuToggle toggle={()=>toggleMenu(isOpen)} isOpen={isOpen} />
<MenuContainer
initial={false}
animate={isOpen ? "open" : "closed"}
variants={menuVariants}
transition={menuTransition}
>
<ContentContainer>
<MobileMenu isOpen={isOpen} />
</ContentContainer>
</MenuContainer>
</HamburgerMenuContainer>
</Theburger>
);
}
The reason you're running into the menu being always visible, is because when react compiles the function, the initial value is used whether it was changed or not. Resulting in the following function to always console log "initial" even when the state has changed.
function A() {
const [textState, setTextState] = useState('initial');
const printState = () => {
console.log(textState);
setTextState('changed');
}
return <button onClick={()=>printState()}>Print me</button>
}
this behaves different in the following two scenarios where either the state is from the parent or the props are given to the function.
Parent Props
function B({textState, setTextState}) {
const printState = () => {
console.log(textState);
setTextState('changed');
}
return <button onClick={()=>printState()}>Print me</button>
}
In function B the printState function is given as a prop and the function is rerendered when the props change, causing also the printState function to be compiled again with the new props causing to console log changed instead of initial.
The other option is handling the state in the component itself and giving the state as a prop to our function.
function C() {
const [textState, setTextState] = useState('initial');
const printState = (prop) => {
console.log(prop);
setTextState('changed');
}
return <button onClick={()=>printState(textState)}>Print me</button>
}
Here the prop is given directly to the printState function, while the printState function is not being recompiled the updated state is given as a prop and handled accordingly

how to toggle between two css classes view types with react

I have a List and Grid type display. How do I toggle between them in React. I want to toggle between jsGridView and jsListView classes.
This is the vanilla js of the toggling of classes
const listView = document.querySelector('.list-view');
const gridView = document.querySelector('.grid-view');
const projectsList = document.querySelector('.project-boxes');
listView.addEventListener('click', () => {
gridView.classList.remove('active');
listView.classList.add('active');
projectsList.classList.remove('jsGridView');
projectsList.classList.add('jsListView');
});
gridView.addEventListener('click', () => {
gridView.classList.add('active');
listView.classList.remove('active');
projectsList.classList.remove('jsListView');
projectsList.classList.add('jsGridView');
});
** this is my react file where I have the display items and buttons to toggle. how do I implement the toggle event listeners into my react file**
How do I toggle between the two classes - jsGridVew and jsListView
const [isActive, setIsActive] = useState(false)
const listToggle = () => {
setIsActive(!isActive)
}
<button key={isActive} className="view-btn list-view" title="List View" onClick={listToggle}>
<i className="fal fa-list-ul fa-2x"></i>
</button>
<button className="view-btn grid-view active" title="Grid View">
<i className="fal fa-th-large fa-2x"></i>
</button>
<div className="project-boxes jsGridView">
{!loading && records.length === 0 ? (<h4 style={{ margin: '20px' }} className='center'>No
records, sorry</h4>) : records.map((record, key) => (
<RecordItem key={key} record={record} isFilter={isFilter} filterByWhat={filterByWhat} />
))}
</div>
EDIT: > I also want to add an 'active class on each button on click. I've tried somethings but it doesn't work
I am assuming this div is where you want to toggle between jsGridView and jsListView
<div className="project-boxes jsGridView">
So why not use a state variable to store the class name? Then use the onClick even to set it.
const [cName, setClassName] = useState('jsGridView');
return (
<Fragment>
<button className="view-btn list-view" title="List View" onClick={() => setClassName('jsListView')}>
List View
</button>
<button className="view-btn list-view" title="Grid View" onClick={() => setClassName('jsGridView')}>
Grid View
</button>
<div className={"project-boxes "+cName}>
{!loading && records.length === 0 ? (<h4 style={{ margin: '20px' }} className='center'>No
records, sorry</h4>) : records.map((record, key) => (
<RecordItem key={key} record={record} isFilter={isFilter} filterByWhat={filterByWhat} />
))}
</div>
</Fragment>
)
So here you set your class to jsGridView initially so it renders in grid view by default. But you also have 2 buttons that can flip it between grid and list view.
You can also add an active class to the button if you want.
<button className={"view-btn list-view"+(cName === 'jsListView' ? ' active_btn':'')} title="List View" onClick={() => setClassName('jsListView')}>
List View
</button>
<button className={"view-btn list-view"+(cName === 'jsGridView' ? ' active_btn':'')} title="Grid View" onClick={() => setClassName('jsGridView')}>
Grid View
</button>
If one class is on, and the other is off, you can do
function toggleClass(elem) {
const classList = elem.classList;
classList.toggle('class1');
classList.toggle('class2');
}
Now the on/off status of the two classes is reversed
Also, in your Styles / CSS file, you can add :not on one class and then not need to use the other class, like
#elem.class1 {
color: ...;
font-size: ...;
}
#elem.class2, #elem:not(.class1) {
color: ...;
font-size: ...;
}
So that :not(.class1) has the same styling effects as adding class2
In React, it's uncommon to mix "vanilla" imperative JS and React like this. React provides a clean solution for toggling displayed elements that I would advise you use instead.
Take the following as an example:
// list-view.jsx
const ListView = ({ items=[] }) => {
const itemsElements = items.map(item => {
return (<li>{item}</li>)
});
return (<ul>{itemsElements}</ul>)
}
// grid-view.jsx
const GridView = ({ items=[] }) => {
const itemsElements = items.map(item => {
return (<span>{item} </span>)
});
return (<div>{itemsElements}</div>)
}
// list-grid-view.jsx
const ListGridView = ({ items=[] }) => {
const [listView, setListView] = React.useState(true);
// this fn toggles the listView variable
const toggleListView = React.useCallback(() => {
setListView(!listView);
}, [listView, setListView]);
return (
<div>
<button onClick={toggleListView} >Toggle!</button>
{listView ? <ListView items={items} /> : <GridView items={items} />}
</div>
);
}
const items = ['Hello', 'World', '!'];
const element = <ListGridView items={items} />
ReactDOM.render(element, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Close dropdown menu on scroll

I am currently working on a project using Ant Design and on one page I have a dropdown Menu. When you click on the menu it opens the dropdown but if you scroll down the menu still keeps open. I wish to implement that when the user is scrolling down the menu closes.
I tried to implement a handleScroll() function in order to use is with the provided prop onVisibleChange. However I am not sure what I should add in the function to get it work.
handleScroll = (e) => {
window.addEventListener('scroll', () => {
console.log('scrolling');
})
}
<Dropdown onVisibleChange={visible => this.handleScroll(
console.log(visible)) } trigger={['click']} overlay={
<Menu>
<Menu.Item key="1"
onClick={() => this.scrollTo(this.whyRef)}>
<Icon icon={u1F427} /> <strong>WHY</strong>
</Menu.Item>
</Menu>
}>
<Dropdown>
You need to add the event listener in the constructor and handle the visibility state of the dropdown yourself. Try something like this:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false
};
window.addEventListener("scroll", this.closeMenu);
}
toggleMenu = () => {
this.setState({ visible: !this.state.visible });
};
closeMenu = () => {
this.setState({ visible: false });
};
render() {
return (
<div>
<Dropdown
overlay={menu}
onVisibleChange={this.toggleMenu}
visible={this.state.visible}
trigger={["click"]}
>
<a className="ant-dropdown-link" href="#">
Click me <Icon type="down" />
</a>
</Dropdown>
</div>
);
}
}
Working example: https://codesandbox.io/s/2ovjzwqz9y

Categories