Я хотел бы сделать раскрывающийся список для выбора из списка элементов, которые всегда имеют ширину самого широкого элемента. Выбранный элемент — это элемент, который всегда отображается, а другие параметры отображаются при наведении.
Это простой раскрывающийся список CSS, и в идеале я хотел бы видеть чистое решение CSS. Поскольку я использую React, а этот раскрывающийся список является компонентом, решение js было бы приемлемым (без использования jQuery или других библиотек, если это возможно).
const Dropdown = (props) => (
<div className = "dropdown">
<div className = "dropdown-item">{props.active}</div>
<div className = "dropdown-body">
{props.items
.filter(x => x !== props.active)
.map(x => <div className = "dropdown-item">{x}</div>)}
</div>
</div>
)
var items = [
"abc", "abcdcdssd", "a"
]
ReactDOM.render(
<div>Hello <Dropdown items = {items} active = {"abc"} /> world.</div>,
document.querySelector("#app")
)
.dropdown {
display: inline-block;
background-color: blue;
text-align: center;
}
.dropdown .dropdown-item {
background-color: red;
width: auto;
padding: 0 6px;
}
.dropdown .dropdown-body {
display: none;
position: absolute;
z-index: 1000;
}
.dropdown:hover .dropdown-body {
display: block;
}
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id = "app"></div>
В приведенном выше примере я хотел бы, чтобы элемент "abc"
имел ширину самого большого элемента "abcdcdssd"
.
Чистым решением css было бы присвоить вашему контейнеру width: auto
и использовать стиль visibility
вместо display
для ваших элементов, чтобы скрыть их:
const Dropdown = (props) => (
<div className = "dropdown">
<div className = "dropdown-item">{props.active}</div>
<div className = "dropdown-body">
{props.items
.filter(x => x !== props.active)
.map(x => <div className = "dropdown-item">{x}</div>)}
</div>
</div>
)
var items = [
"abc", "abcdcdssd", "a"
]
ReactDOM.render(
<div>Hello <Dropdown items = {items} active = {"abc"} /> world.</div>,
document.querySelector("#app")
)
var items = [
"abc", "abcdcdssd", "a"
]
ReactDOM.render(
<div>Hello <Dropdown items = {items} active = {"abc"} /> world.</div>,
document.querySelector("#app")
)
.dropdown {
display: inline-block;
text-align: center;
vertical-align: top;
width: auto;
}
.dropdown .dropdown-item {
background-color: red;
padding: 0 6px;
}
.dropdown .dropdown-body {
visibility: hidden;
z-index: 1000;
}
.dropdown:hover .dropdown-body {
visibility: visible;
}
<script crossorigin src = "https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src = "https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id = "app"></div>
Предупреждение заключается в том, что, поскольку элементы скрыты, только если вы выберете текст, ваши скрытые элементы также будут выбраны.
Вы можете измерить ширину раскрывающегося списка после первоначального рендеринга и применить его к активному элементу раскрывающегося списка. Чтобы это работало, вам изначально нужно использовать visibility: hidden
вместо display: none
, потому что можно измерить только ширину скрытых элементов. Вы можете скрыть их, как только они будут измерены изначально.
В приведенном ниже примере используются реагирующие хуки, для которых требуется версия >16.8
, но это также может быть достигнуто с помощью компонента на основе класса, использующего componentDidMount
.
const {useRef, useState, useLayoutEffect} = React;
const Dropdown = props => {
const bodyRef = useRef();
const [bodyWidth, setWidth] = useState(null);
const [itemsHidden, setItemsHidden] = useState(false);
useLayoutEffect(() => {
setWidth(bodyRef.current.clientWidth);
setItemsHidden(true);
}, []);
return (
<div style = {{width: bodyWidth || 'auto'}} className = "dropdown">
<div className = "dropdown-item">{props.active}</div>
<div ref = {bodyRef} className = {`dropdown-body${itemsHidden ? ' hidden' : ''}`}>
{props.items
.filter(x => x !== props.active)
.map((x, idx) => (
<div key = {idx} className = "dropdown-item">{x}</div>
))}
</div>
</div>
);
};
var items = [
"abc", "abcdcdssd", "a"
]
ReactDOM.render(
<div>Hello <Dropdown items = {items} active = {"abc"} /> world.</div>,
document.querySelector("#app")
)
.dropdown {
display: inline-block;
background-color: blue;
text-align: center;
}
.dropdown .dropdown-item {
background-color: red;
width: auto;
padding: 0 6px;
}
.dropdown .dropdown-body {
position: absolute;
z-index: 1000;
}
.dropdown .dropdown-body.hidden {
display: none;
position: absolute;
z-index: 1000;
}
.dropdown:hover .dropdown-body {
display: block;
}
<script crossorigin src = "https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src = "https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id = "app"></div>
@MarošBeťko Их возвращают хуки useState
. Вам нужно react>=16.8
использовать хуки.
О, теперь я вижу, что вы используете 2 состояния в основном, одно для ширины и одно для скрытых элементов. Я внедрил ваше решение в свой проект, но по какой-то причине первый элемент не занимает ширину самого широкого элемента списка.
@MarošBeťko Если вы поделитесь своим кодом в codeandbox, я могу взглянуть на него. Возможно, вы просто что-то упустили из моего ответа. Дважды проверьте, что у вас одинаковый код и css.
Когда вызывается обратный вызов useLayoutEffect
, ширина клиента равна 0. В приложении, когда это раскрывающееся меню впервые добавляется, оно не отображается и скрыто под элементом display: none
.
@MarošBeťko Хорошо, если это 0
, это означает, что у элемента body нет содержимого. В обязательном порядке необходимо изначально визуализировать отображаемые элементы, чтобы можно было измерить их ширину. Вот почему я только скрыл их, применив класс hidden
css после измерения. Также при динамическом обновлении элементов вам придется снова применять эффект макета. В моем примере он будет запущен только один раз после монтирования компонента.
а что, если выпадающее меню нужно сначала скрыть? как я могу обновить ширину после того, как она станет видимой?
@MarošBeťko Я обновлю свой ответ более сложным примером, который может обрабатывать обновления и крайние случаи, как только у меня будет на это время. Наверное, вечером.
Я пытаюсь использовать реагирующее решение, но я не уверен, как должны работать
setWidth
илиsetItemsHidden
. Они нигде не определены.