I have multiple dropdowns. Now when i open one dropdown. and then open the second one. Both are open and the content is the same. I want, if the user want to open a second one the first one will close.
{this.state.MainChannels.map(item => {
return (
<Category key={item.name} >
<CategoryContainer >
<CheckboxLabel>
<CheckboxInput type="checkbox" checked={item.followed} onChange={() => this.handleCheckAllSubCategories(item)} >
</CheckboxInput>
<CheckboxButton className="fa fa-check" >
</CheckboxButton>
</CheckboxLabel>
<CategoryTitle>
{item.name}
</CategoryTitle>
<ChannelImage style={{ backgroundImage: `linear-gradient(to right, rgba(27,31,31,1) , rgba(255,255,255,0)), url(${item.image})` }}>
</ChannelImage>
</CategoryContainer>
<OpenInput type="checkbox" onClick={() => this.handleChannel(item.name)}>
</OpenInput>
<OpenButton className="fa fa-chevron-down" >
</OpenButton>
{subChannels.map((item2, i): any => {
return (
<Subcategories key={i} >
<SubCheckboxLabel>
<SubCheckboxInput id={item2.name} type="checkbox" onChange={() => this.handleCheckbox(item2.name)} checked={item2.followed}>
</SubCheckboxInput>
<SubCheckboxButton className="fa fa-check" >
</SubCheckboxButton>
<SubCategoryTitle>
{item2.name}
</SubCategoryTitle>
<div>
</div>
</SubCheckboxLabel>
</Subcategories>)
})}
</Category>
);
}
const OpenInput = styled.input`
position: absolute;
top: 0px;
right: 0px;
height: 50px;
width: 40px;
z-index: 1000;
opacity: 0;
cursor:pointer;
&:checked ~ p {
transform: rotate(180deg);
}
&:checked ~ ul {
display: block;
}
const OpenInput = styled.input`
position: absolute;
top: 0px;
right: 0px;
height: 50px;
width: 40px;
z-index: 1000;
opacity: 0;
cursor:pointer;
&:checked ~ p {
transform: rotate(180deg);
}
&:checked ~ ul {
display: block;
}
`
My Problem is this last block. if the user clicks on Open input. The "ul" parts will be displayed. How can i replace this with some JS?
Here a picture what the problem is:
We see there is the same content. That should not be it should close one dropdown
It's hard to tell from this code how you're deciding what is open or closed as we can't see what your handlers are doing, but I'd suggest using state to keep track of which item is open based on the item name (assuming it is unique).
Then it would be as simple as passing a boolean to your dropdown, 'isOpen' for example, based on the current item in the state, which would be updated by the event handler.
Here is the example with accordions that i was talking about in the comment section. You can use the same principle to open/close your dropdown :
.ts :
import React, { Component } from "react";
import { render } from "react-dom";
import "./style.css";
const App = () => {
const toggleAccordion = e => {
Array.from(document.getElementsByClassName('accordion')).forEach(el => el.classList.add('hidden'));
e.target.classList.remove("hidden");
};
return (
<>
<div className="accordion hidden" onClick={toggleAccordion}>
accordion header
<div className="accordion-content-wrapper">
<div>Content</div>
<div>Content</div>
<div>Content</div>
</div>
</div>
<div className="accordion hidden" onClick={toggleAccordion}>
accordion header 2
<div className="accordion-content-wrapper">
<div>Content</div>
<div>Content</div>
<div>Content</div>
</div>
</div>
</>
);
};
render(<App />, document.getElementById("root"));
.css (not needed with your dropdown i think):
.accordion.hidden {
height: 18px;
overflow: hidden;
}
.accordion-content-wrapper{
margin-left: 10px;
border-left: 1px solid black;
}
Now, as i said it may not fit your needs since we don't have enough code to help you. As Rayan Wolton said, th way you handle the open/close aciton on your dropdown is important.
Related
I have a component that gets rendered when a photo is clicked with CSSTransition. It also has a Backdrop component but upon click only the backdrop gets shown.
If I remove CSSTransition then it behaves as expected.
Here's the photo viewer component:
import styles from './PhotoZoomViewer.module.scss';
import { CSSTransition } from 'react-transition-group';
import transitions from './PhotoZoomViewerTransition.module.scss';
import PhotoZoomBackdrop from './PhotoZoomBackdrop/PhotoZoomBackdrop';
const PhotoZoomViewer = ({ show, photoUrl, onExit}) => (
<CSSTransition in={show} timeout={500} classNames={transitions} unmountOnExit>
<PhotoZoomBackdrop show={show} />
<div className={styles.photoZoomViewer}>
<img className={styles.zoomedImage} src={photoUrl} alt={photoUrl} onClick={onExit} />
</div>
</CSSTransition>
);
export default PhotoZoomViewer;
Here's PhotoZoomViewer's transition specific SCSS:
.enter {
opacity: 0;
&Active {
opacity: 1;
transition: all 300ms ease-in-out;
}
}
.exit {
opacity: 1;
&Active{
opacity: 0;
transition: all 300ms ease-in-out;
}
}
Here's PhotoZoomViewer's own SCSS:
.photoZoomViewer {
z-index: 300;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
cursor: zoom-out;
}
Here's PhotoZoomBackdrop component:
import React from 'react';
import styles from './PhotoZoomBackdrop.module.scss';
const PhotoZoomBackdrop = ({show}) => (
show && <div className={styles.backdrop}></div>
);
export default PhotoZoomBackdrop;
Here's PhotoZoomBackdrop.module.scss:
.backdrop {
width: 100%;
height: 100%;
position: fixed;
z-index: 100;
left: 0;
top: 0;
background-color: rgba(0, 0, 0, 0.2);
}
Without CssTransition component backdrop and image get displayed but with the CSSTransition backdrop covers the entire page and image doesnt show up.
I had to enclose JSX within CSSTransition within a div:
const PhotoZoomViewer = ({ show, photoUrl, onExit}) => (
<CSSTransition in={show} timeout={500} classNames={transitions} unmountOnExit>
<div>
<PhotoZoomBackdrop show={show} />
<div className={styles.photoZoomViewer}>
<img className={styles.zoomedImage} src={photoUrl} alt={photoUrl} onClick={onExit} />
</div>
</div>
</CSSTransition>
);
I wanted to make the lower menu similar to VK, where you click on the icons and the content changes. I implemented different content, but I can't make each tab have its own icon.
I provide a screenshot and code. Do not pay attention to the style, I first need to understand the logic Using Vue CLI.
<template>
<div id="app">
<font-awesome-icon
icon="user-secret"
v-for="tab in tabs"
:key='tab'
#click="currentTab = tab"
:class="['tab-button', {active: currentTab === tab}]"
>
{{ tab }}
</font-awesome-icon>
<component v-bind:is="currentTabComponent"></component>
</div>
</template>
<script>
import Posts from './Posts.vue'
import List from './List.vue'
export default {
data () {
return {
currentTab: 'Posts',
tabs: ['Posts', 'List'],
icon: 'bell'
}
},
components: {
tabPosts: Posts,
tabList: List
},
computed: {
currentTabComponent() {
return 'tab-' + this.currentTab.toLowerCase()
}
}
}
</script>
<style scoped>
body {
overflow: auto;
padding: 0;
margin: 0;
}
#app {
position: relative;
width: 320px;
height: 400px;
border: solid 1px black;
}
.tab-button.active {
position: relative;
color: red;
height: 50px;
width: 50px;
}
.tab-button {
position: relative;
float: right;
bottom: 10px;
height: 50px;
width: 50px;
}
</style>
Try this:
<div
v-for="tab in tabs"
:key='tab'
#click="currentTab = tab"
:class="['tab-button', {active: currentTab === tab}]"
>
<font-awesome-icon >
icon="user-secret"
</font-awesome-icon >
</div>
also this #click="currentTab = tab" needs to be refactored
I'll assume that this issue has already been solved, but, in any case, here is my contribuition using fontAwesome so you can understand the logic:
Change the definition of your data tabs and currentTab to something like this:
currentTab: {title:'Post section',icon:'bell',page:'Post'},
tabs:{
title:'Post section',
icon:'bell',
page:'Post'
},
{
title:'List of posts',
icon:'list',
page:'List'
}
Convert your font-awesome-icon tag to:
<button v-for="tab in tabs" :key="tab"
:class="['tab-button', { active: currentTab === tab }]" #click="currentTab = tab"
title="tab.title">
<i class="fa" :class="'fa-'+tab.icon"></i>
</button>
Finally change you component tag to:
<component :is="currentTab.page" class="tab"></component>
And them you can ignore the whole compuded: {} section of your export default
I have made a modal in a component, the data works fine, it's dynamic which is perfect. Problem is, when I open it, it seems that whenever I click anywhere inside the modal, it closes it again.
The modal is managed using useState hook. I think the problem lies in my onClick calls further down. Any advise please?
const LeaveRequestsUnits = () => {
let [data, setData] = useState([]);
let [modalState, setModalState] = useState(false);
let modalOnOff = () => {
setModalState(!modalState);
};
let [selectedUnit, setSelectedUnit] = useState('');
let updateSelectedUnit = (item) => {
setSelectedUnit(item);
const getLeaveUnits = data.map((item) => {
// fct to update the modalState and display-block the modal
const openModal = (item) => {
updateSelectedUnit(item);
modalOnOff();
$('.modalBackground').css('display', 'block');
};
const modal = () => {
return (
<div>
<p>{selectedUnit.note}</p>
<p>{selectedUnit.start}</p>
<p>{selectedUnit.end}</p>
Google
<h1>Close</h1>
</div>
);
};
// display:none the modal if the modalState is false
if (!modalState) {
$('.modalBackground').css('display', 'none');
}
if (item.end >= today && item.approved !== false) {
return (
<div
className={unitColour}
key={item.reqID}
onClick={() => openModal(item)}
>
<div className='unitLeft'>
<img src={statusIcon} alt='Status Icon' id='statusIcon' />
</div>
<div className='unitMiddle'>
<p id='unitLeaveType'>{leaveTypeName}</p>
<p id='unitDate'>{startEndDate(item.start, item.end)}</p>
</div>
<div className='unitDivider'></div>
<div className='unitRight'>
<p id='unitDuration'>
{convertTimestamp(item.duration, item.type)}
</p>
</div>
{/* modal */}
<div className={`modalBackground modalShowing-${modalState}`}>
{modal()}
</div>
{/* end modal */}
</div>
);
}
});
return <div className='requestsContainer
CSS below:
.modalBackground {
display: none;
z-index: 10000;
width: 80vw;
height: 250px;
background-color: white;
border-radius: 15px;
position: absolute;
top: 10vh;
overflow: hidden;
color: black;
opacity: 0;
pointer-events: none;
cursor: auto;
}
.modalShowing-true {
/* display: none;
position: absolute;
top: 0;
width: 100%;
height: 100%;
background-color: black;
opacity: 0.3; */
opacity: 1;
pointer-events: auto;
}
When you define the modal component, you need to tell it to prevent clicks on it from bubbling up to the parent elements click listener that will try to close the modal.
const cancelClick = useEffect( (event) => {
event && event.stopPropagation();
}, []) // only need to create this function once
const modal = () => {
return (
<div onClick={cancelClick}>
<p>{selectedUnit.note}</p>
<p>{selectedUnit.start}</p>
<p>{selectedUnit.end}</p>
Google
<h1>Close</h1>
</div>
);
};
I would HIGHLY recommend you stop using jquery here as well. In this component its a super easy change, just remove the jquery calls to change the display css property on the backdrop and instead use the state variable to control showing that.
<div className={`modalBackground${!!modalState ? ' open' : ''}`}>
{modal()}
</div>
and then you can clean up the css for it. I dropped the display: none; style and went with transform: scale(0);. This gives more flexibility in how you decide how you want to show the modal (fade in would do nicely).
.modalBackground {
position: absolute;
top: 10vh;
z-index: 10000;
overflow: hidden;
width: 80vw;
height: 250px;
opacity: 0;
transform: scale(0);
background-color: white;
color: black;
border-radius: 15px;
cursor: auto;
/* here you can add a transition on both opacity and scale to make the modal animate in. */
}
.modalBackground.open {
opacity: 1;
transform: scale(1);
}
I would like to add some transition styling to my side navigation on my app. I am able to do this using normal classes however in this tutorial they use css modules and i am unsure how to do this using css modules.
I would like my nav to glide in and out, at the moment it jumps statically when the onClick function fires - toggleSideDrawer.
I have used this logic but I am not sure if it is doing anything:
className={props.toggleSideDrawer ? classes.SideDrawerOpen : classes.SideDrawer
Essentially i want that when the user clicks the toggle, the transform property switches from translateX(-100%) to translateX(0) but this is not happening.
Side nav code:
import React from "react";
import Logo from "../../Logo/Logo";
import NavigationItems from "../NavigationItems/NavigationItems";
import Backdrop from "../../UI/Backdrop/Backdrop";
import Aux from "../../../hoc/Aux";
import classes from "./SideDrawer.css";
const SideDrawer = props => {
return (
<Aux classname={classes.SideDrawer}>
<Backdrop
showBackdrop={props.showSideDrawer}
clicked={props.toggleSideDrawer}
/>
{props.showSideDrawer && (
<div
onClick={props.toggleSideDrawer}
className={
props.toggleSideDrawer ? classes.SideDrawerOpen : classes.SideDrawer
}
>
<div className={classes.Logo}>
<Logo />
</div>
<nav>
<NavigationItems />
</nav>
</div>
)}
</Aux>
);
};
export default SideDrawer;
Where the code is used in my Layout component:
import React, { useState } from "react";
import Aux from "../Aux";
import classes from "./Layout.css";
import Toolbar from "../../components/Navigation/Toolbar/Toolbar";
import SideDrawer from "../../components/Navigation/SideDrawer/SideDrawer";
const layout = props => {
const [showSideDrawer, setShowSideDrawer] = useState(false);
return (
<Aux>
<SideDrawer
showSideDrawer={showSideDrawer}
toggleSideDrawer={() => {
setShowSideDrawer(!showSideDrawer);
}}
/>
<Toolbar
onMenuClick={() => {
setShowSideDrawer(!showSideDrawer);
}}
/>
<main className={classes.mainContent}> {props.children} </main>
</Aux>
);
};
export default layout;
CSS:
.SideDrawer {
position: fixed;
width: 280px;
max-width: 70%;
height: 100%;
left: 0;
top: 0;
z-index: 200;
background-color: white;
padding: 32px 16px;
box-sizing: border-box;
transform: translateX(-100%);
}
#media (min-width: 500px) {
.SideDrawer {
display: none;
}
}
.Logo {
height: 11%;
text-align: center;
}
.SideDrawerOpen {
position: fixed;
width: 280px;
max-width: 70%;
height: 100%;
left: 0;
top: 0;
z-index: 200;
padding: 32px 16px;
box-sizing: border-box;
background-color: red;
transform: translateX(0);
transition: transform 0.3s ease-out;
}
The thing is that you need the element will has the transition rule all the time.
My suggestion is to set a static class which which will hold all the styles and addd another one only for overriding transform to make it move.
Something like that (it uses scss but it's easy to do it with css)
.SideDrawer {
position: fixed;
width: 280px;
max-width: 70%;
height: 100%;
left: 0;
top: 0;
z-index: 200;
background-color: white;
padding: 32px 16px;
box-sizing: border-box;
transition: transform .3s ease;
transform: translateX(-100%);
&.show {
transform: translateX(0);
}
}
export const App = () => {
const [showSideDrawer, setShowSideDrawer] = useState(false);
const sidebarClasses = classname([
styles.SideDrawer,
{
[styles.show]: showSideDrawer
}
]);
const ToggleSidebar = () => {
return (
<button onClick={() => setShowSideDrawer(!showSideDrawer)}>
Toggle Sidebar
</button>
);
};
return (
<Fragment>
<h1>App</h1>
<div className={sidebarClasses}>
<div>Sidebar content</div>
<ToggleSidebar />
</div>
<ToggleSidebar />
</Fragment>
);
};
https://codesandbox.io/s/xenodochial-framework-04sbe?file=/src/App.jsx
#MoshFeu helped me fix this.
The problem is that you render the drawer only when showSideDrawer so before it becomes true, the sidebar is not in the DOM yet so the transition is not affecting it.
The solution is to keep it in the DOM all the time but toggle . Open class to change the style.
There are libraries that knows to make the transition works even for elements that are not in the DOM but it's a bit more complicated.
code fix for SideDrawer.js without the conditional within the return
class SideDrawer extends Component {
render() {
let sideDrawerClass = [classes.SideDrawer];
// SideDrawer will now be an array with the side drawer classes and the open class
if (this.props.showSideDrawer) {
sideDrawerClass.push(classes.Open);
}
return (
<Aux classname={classes.SideDrawer}>
<Backdrop
showBackdrop={this.props.showSideDrawer}
clicked={this.props.toggleSideDrawer}
/>
<div
className={sideDrawerClass.join(" ")}
onClick={this.props.toggleSideDrawer}
>
<div className={classes.Logo}>
<Logo />
</div>
<nav>
<NavigationItems />
</nav>
</div>
</Aux>
);
}
}
export default SideDrawer;
I created a dropdown menu shown below and whenever it is visible, the buttons beneath it are still active/visible. How do I make it so that the dropdown menu is the most top layer? the partially visible ellipsis buttons are functional which is not what I want.
Dropdown.jsx
const Dropdown = props => {
const [showOptions, setShowOptions] = useState(false);
const toggleOptions = e => {
e.preventDefault();
setShowOptions(!showOptions);
}
const buttonLabel = (<i className='fa fa-ellipsis-v' />)
return (
<div className='employees-li-options'>
<FormButton
label={buttonLabel}
id='employees-li-options-button'
type='button'
onClick={toggleOptions}
/>
{
showOptions ? (
<div className='employees-li-options-visible'>
<FormButton
type='button'
label='active'
/>
<FormButton
type='button'
label='inactive'
/>
<FormButton
type='button'
label='deactivated'
/>
<FormButton
type='button'
label='invite pending'
/>
</div>
) : (
null
)
}
</div>
)
};
css
.employees-li-options-visible {
position: absolute;
background-color: $white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: space-evenly;
border-radius: 10px;
box-shadow: 0 0 5px 1px $light-gray1;
padding: 1em 1em 0.1em 1em;
}
You can use CSS. Just add z-index style to the parent OR to the dropdown itself. The parent element should still be visible behind the dropdown.
z-index.
// To the parent
#parent {
z-index: -1;
}
// To the dropdown
.employees-li-options-visible {
z-index: 1000;
}