Reputation: 18441
I've developed the following React DropdownList component using styled-components
and flexbox:
import React from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCaretDown } from "@fortawesome/free-solid-svg-icons";
const options = [
{
key: "opt1",
value: "Option 1",
},
{
key: "opt2",
value: "Option 2",
},
{
key: "opt3",
value: "Option 3",
},
{
key: "opt4",
value: "Option 4",
},
{
key: "opt5",
value: "Option 5",
},
{
key: "opt6",
value: "Option 6",
},
{
key: "opt7",
value: "Option 7",
},
];
const DropdownListContainer = styled.div`
position: relative;
display: inline-block;
${(props) =>
props.disabled &&
`
opacity: 0.6;
`}
`;
const DropdownListButton = styled.button`
display: flex;
align-items: flex-start;
width: 100%;
background-color: white;
padding: ${(props) =>
props.collapse ? "7px 8px 7px 8px" : "8px 8px 8px 8px"};
border: 1px solid black;
border-radius: 4px;
cursor: pointer;
overflow: hidden;
}`;
const DropdownListButtonTitle = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}`;
const DropdownListContentDiv = styled.div`
display: ${(props) => (props.collapsed ? "none" : "absolute")};
min-width: 160px;
position: relative;
background-color: white;
border: 1px solid #fefefe;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 10;
}`;
const OptionDiv = styled.div`
width: 100%;
padding: 4px 0px 4px 4px;
overflow: hidden;
&:hover {
background-color: #020202;
color: white;
}
`;
class DropdownList extends React.Component {
static propTypes = {
onChange: PropTypes.func,
onDropdownListClick: PropTypes.func,
value: PropTypes.any,
options: PropTypes.array,
disabled: PropTypes.bool,
large: PropTypes.bool,
};
state = {
collapsed: true,
};
handleButtonClick = () => {
this.setState({
collapsed: !this.state.collapsed,
});
};
handleSelect = (id) => {
if (this.props.onChange) this.props.onChange(id);
this.setState({
collapsed: true,
});
};
render = () => {
let { value, disabled, large, readOnly } = this.props;
// let { options } = this.props; // Using hardcoded options for testing
let { collapsed } = this.state;
let optionsList = [];
let val = null;
if (options && options.length > 0) {
options.forEach((option) => {
let content = option.value;
optionsList.push(
<OptionDiv
key={option.key}
onClick={() => this.handleSelect(option.key)}
large={large}
>
{content}
</OptionDiv>
);
if (value === option.key) val = option.value;
});
}
if (!val && options && options.length > 0) val = options[0].value;
let buttonContent = (
<DropdownListButtonTitle>
<div>{val}</div>
<div>
<FontAwesomeIcon icon="faCaretDown" size="small" />
</div>
</DropdownListButtonTitle>
);
return (
<DropdownListContainer disabled={disabled}>
<DropdownListButton
onClick={this.handleButtonClick}
disabled={readOnly}
>
{buttonContent}
</DropdownListButton>
<DropdownListContentDiv collapsed={collapsed}>
{optionsList}
</DropdownListContentDiv>
</DropdownListContainer>
);
};
}
export default DropdownList;
It is working fine, except for a styling problem. If I use inside a content, when I open the menu it shifts all content down. The same behaviour happens when I use it at the end of the view screen. It increases the view height and pops up a scrollbar.
I do expect it to "float" over the content, showing the content above it. At the edges of the screen I expect it to open in the opposite direction (up if I'm at the screen bottom and left if I'm at the screen rightmost position).
Upvotes: 2
Views: 935
Reputation: 599
DropdownListContentDiv shouldn't get display "none" or "absolute", it should get display "none" or "block". After that, position of that content is not "relative", but "absolute", like this:
const DropdownListContentDiv = styled.div`
display: ${(props) => (props.collapsed ? "none" : "block")};
min-width: 160px;
position: absolute;
background-color: white;
border: 1px solid #fefefe;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 10;
}`;
Upvotes: 2