Задание min-width флекс элемента на основании min-width содержимого
Всем привет. Вопрос следующий. Имеется флекс-контейнер, допустим ограниченный 800 px. Имеется динамическое количество флекс-элементов, состоящих из label и span, содержимое которых также является динамическим. Мне необходимо, если табы полностью не помещаются в контейнер начинать сокращать label во флекс-элементах. При этом ширина не должна стать меньше 3 символов + многоточия (это приблизительно достигается за счет min-width: 6ch). На данный момент если у какого-то таба очень длинное название, начинают уменьшаться другие табы, что вызывает переполнение флекс-элемента и span обрезается. При этом другие табы еще не достигли своего минимального значения (min-width лейбла + gap + span). Захардкодить min-width таба само собой не вариант - контент динамический (span может содержать разные числа). Различные значения min-width таба (min-content, fit-content) вообще не дают тексту обрезаться. Прилагаю изображения, демонстрирующие проблему, ссылку на CodeSandbox и примерный код. Буду благодарен за любую помощь. Ссылка на codesandbox
Табам хватает места (все хорошо)

Длинное название у таба - начинают вылезать span у других табов (хотя min-width лейбла не достигнут)

Ожидаемое поведение (тут хардкод min-width табов (само собой такое решение не подходит))
import styled from "@emotion/styled";
const TabsList = styled.ul`
list-style: none;
display: flex;
justify-content: flex-start;
gap: 20px;
margin: 0;
margin-left: 20px;
width: 100%;
max-width: 900px;
background: yellowgreen;
/* because the first tab always will be "all" */
li:first-of-type label {
min-width: 20px;
}
`;
const singleNumPaddingStyles = "0 8px";
const KeywordTab = styled.li`
position: relative;
overflow: hidden;
display: flex;
align-items: center;
padding-bottom: 4px;
gap: 8px;
label {
display: block;
font-weight: 400;
line-height: 23px;
cursor: pointer;
user-select: none;
text-transform: capitalize;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
color: blue;
}
/* trying to set minimum 3char + ... */
min-width: 6ch;
}
span {
color: white;
line-height: 23px;
background-color: pink;
user-select: none;
padding: ${({ singleNum }) =>
singleNum ? singleNumPaddingStyles : "0 4px"};
border-radius: 4px;
}
`;
const Group = ({ label, number }) => (
<KeywordTab singleNum={number < 10}>
<label>{label}</label>
<span>{number}</span>
</KeywordTab>
);
export const View = ({ dictionaries }) => {
//logic (useLayoutEffect)
return (
<TabsList>
{dictionaries.map(({ label, total }, index) => (
<Group key={index} label={label} number={total} />
))}
</TabsList>
);
};
html {
position: relative;
display: flex;
flex-direction: column;
flex: 1;
min-height: 100%;
font-size: 10px;
}
body {
display: flex;
flex-direction: column;
flex: 1;
width: 100%;
background: #f6f6f6;
color: #454545;
text-shadow: 1px 1px 1px rgb(0, 0, 0, 0%);
min-height: 100%;
overflow-x: hidden;
font: 300 18px/26px "Yantramanav", "Helvetica", "Noto", serif;
}
* {
margin: 0;
padding: 0;
border: 0;
box-sizing: border-box;
}
Ответы (1 шт):
Добился предполагаемого поведения перейдя от flexbox к grid.На родительском контейнере стал динамически задавать количество столбцов (кроме первого, ибо по логике предполагалось, что там постоянно элемент - all),а ширину определять выражением minmax(min-content, max-content). В отдельном элементе сделал grid из двух столбцов, где 1-ый - 1fr, 2-ой - min-content.
import styled from "@emotion/styled";
import { useLayoutEffect, useRef, useState, forwardRef } from "react";
export const TabsListGrid = styled.ul`
margin: 0;
width: 100%;
display: grid;
list-style: none;
background: yellowgreen;
grid-gap: 20px;
grid-template-columns: min-content repeat(${({ columns }) => columns - 1}, minmax(min-content, max-content));
max-width: 712px;
li:first-of-type label {
min-width: min-content;
}
`
const singleNumPaddingStyles = "0 8px";
const KeywordTab = styled.li`
flex-shrink: 0;
position: relative;
overflow: hidden;
display: grid;
grid-template-columns: 1fr min-content;
align-items: center;
padding-bottom: 4px;
grid-gap: 8px;
label {
font-weight: 400;
line-height: 23px;
cursor: pointer;
user-select: none;
text-transform: capitalize;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover {
color: blue;
}
min-width: 6ch;
}
span {
color: white;
line-height: 23px;
background-color: pink;
user-select: none;
padding: ${({ singleNum }) =>
singleNum ? singleNumPaddingStyles : "0 4px"};
border-radius: 4px;
}
`;
const Group = forwardRef(({ label, number }, ref) => (
<KeywordTab ref={ref} singleNum={number < 10}>
<label>{label}</label>
<span>{number}</span>
</KeywordTab>
));
export const View = ({ dictionaries }) => {
const [visible, setVisible] = useState(true);
const tabsRef = useRef(null);
const tabRef = useRef([]);
useLayoutEffect(() => {
if (tabsRef?.current?.getBoundingClientRect().width < 500) {
setVisible(false);
} else {
setVisible(true);
}
console.log(tabRef);
}, []);
return (
<>
{visible && (
<TabsListGrid ref={tabsRef} columns={dictionaries.length}>
{dictionaries.map(({ label, total }, index) => (
<Group
ref={(el) => (tabRef.current[index] = el)}
key={index}
label={label}
number={total}
/>
))}
</TabsListGrid>
)}
</>
);
};