Shrink of inside label in a flex item - css

Hello everybody I need a help. The issue is the following: I have flex-container with tabs (number of tabs can be different as they are passed as array in React component).
Each tab contains label and span with a number. By initial conditions for this task, label should not be shorter than 3 letters + "..." (we apply ellipsis for it).
As I have understood, there is no other solution but to do it manually from code (ch unit is based on symbol 0, that's why this approach gives inaccurate result). But let's move to key issue. Text in label can have different length, and we can have different number of tabs.
I need to set tabs in container (let it be restricted by max-width of 900px) as long as it possible. What does it mean: we set tabs with full length of label if it is possible, if not - label shrinks until it reaches min-width (6ch). If number of tabs is too large (all labels have min-width, but tabs exceed container), I will not render them at all. I am going to implement that with help of useLayoutEffect with checks of exceeding container there.
The main problem now is that spans overflow tabs, in other words labels have possibility to shrink, but instead of that other tabs start shrinking arising problems with span. I have tried to use grid with templates columns of 1fr width (number of columns I can set by passing length of array to styled component). That works, but I need to have by tabs aligned to left side (instead of that they would take all available space) and I have problems with extra empty space if label + gap + span < 1fr of container.
By this moment I have no solution, but to hardcode min-width of tab, but all of us understand that it is unacceptable (not to mention the fact that there can be 10,000, for example, in span). I am asking for any help. I would be the happiest person if I found solution.
I have attached images with demonstration of issue, code, and link to codeSandbox with example (there you can insert tabs in mock_data, change length of words).
CodeSandBox - https://codesandbox.io/s/gracious-dijkstra-61s9sp?file=/src/Component.jsx:0-1606
tabs have enough space
labels can shrink, but instead spans overflow tabs
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>
);
};
//very-very-very bad desicion: hardcode min-width
// of tab ~ 88px (53px for the first - it will always be "all")

I have achieved approximately desired behaviour by using grid instead of flexbox. On parent container i've started to set number of columns dynamically and give them width of minmax(min-content, max-content) (except for the first column as there is always expected element - All). Separate tab was rewritten to grid with 2 columns - (1fr min-content respectively)
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>
)}
</>
);
};

Related

Is there any way i can style an element based on other elements state using styled components?

I just started using styled-components and currently converting all my css into styled components in a project. But i am not sure how to achive some things and i could not find anything relevant in documentation too. I would like to know if the following problem is achievable using this approach in styled-components.
I have two checkboxes(radio input), if its checked i want to style a span element(Dot component in this case).
<div className="acc__type">
<RadioInput type="radio" name="accType" id="dot-1" required />
<RadioInput type="radio" name="accType" id="dot-2" required />
<Category>
<AccTypeLabel for="dot-1">
<Dot></Dot>
<AccTypeTitle>Student</AccTypeTitle>
</AccTypeLabel>
<AccTypeLabel for="dot-2">
<Dot></Dot>
<AccTypeTitle>Staff</AccTypeTitle>
</AccTypeLabel>
</Category>
</div>
here are my styled components
export const RadioInput = styled.input`
display: none;
`;
export const Category = styled.div`
display: flex;
width: 80%;
margin: 15px 0;
justify-content: space-between;
`;
export const AccTypeLabel = styled.label`
display: flex;
align-items: center;
`;
export const AccTypeTitle = styled.div`
font-size: 20px;
font-weight: 500;
`;
export const Dot = styled.span`
height: 18px;
width: 18px;
background: #d9d9d9;
border-radius: 50%;
margin: 10px;
border: 5px solid transparent;
transition: all 0.3s ease;
//here iam trying to change this components styles based on <RadioInput/> components state.
#dot-1:checked & {
border-color: #d9d9d9;
background: #9b59b6;
}
`;
in css i did this and it worked
#dot-1:checked ~ span,
#dot-2:checked ~ span {
border-color: var(--sub-grey);
background: var(--main-purple);
}
/* <Dot/> component was span here */
any help!
In Styled Components it's an ever so slightly different syntax, the reason is that it hashes the class names in a way that makes it difficult to target them. So this is how you do it in a way that ensures SC knows what's what:
const dot1StyledComponent = styled.div`
...
`;
${ dot1StyledComponent }:checked ~ span {
border-color: #d9d9d9;
background: #9b59b6;
}

How can I create a container with equal padding between wrapping elements and the border

I am creating a container (which needs to behave responsively for multiple device widths) which can contain a number of different tags. The container needs to have either a consistent padding around the edges and between the rows of wrapped elements, or the elements should have the appropriate margins in place to behave in the same way.
I'm having difficulty getting it to behave how I want and currently it looks like this:
As you can see the top row is perfect; however, the second row has two issues:
The space between the element in the second row and the elements in the first row is twice what it needs to be.
The second row does not have any space on the left of the element.
Currently this is my code:
// Components
const InputContainer = styled.ul`
position: relative;
display: flex;
flex-wrap: wrap;
width: 100%;
border: solid 0.06rem ${colours.darkBluePrimary};
border-radius: 0.6rem;
`;
const Input = styled.input`
appearance: none;
font-family: inherit;
font-size: 1.6rem;
line-height: 1.25;
padding: 1rem;
background-color: white;
border: none;
border-radius: 0.6rem;
&:focus {
outline: none;
}
`;
const Tag = styled.li`
display: flex;
align-items: center;
justify-content: space-between;
margin: 1rem 1rem 1rem 0;
padding: 1rem;
font-family: inherit;
font-size: 1.6rem;
color: white;
background-color: ${colours.darkBluePrimary};
border-radius: 0.6rem;
&:first-child {
margin-left: 1rem;
}
`;
Obviously my approach for using the &:first-child selector will not work. But I am struggling to figure out a way for this to work.
What you are currently doing is having the left margin be 0 and all the other margins be 1rem. This means that the first column doesn't have a left margin either, but you added one manually in
&:first-child {
margin-left: 1rem;
}
You can do this for the second row as well, but that would not solve the 2rem spacing between the rows. So what I think you should do is make all margins 0.5rem, then add padding-left and padding-top to the parent element (the container), and set those values at 0.5rem. Hope this helps!

First letter not working if -webkit-box display property is set

I want to display the first letter in red color but I've noticed when I using display: -webkit-box the first-letter property may not works anymore.
Expected behavior :
Apply style to first-letter (in CSS only) and keep the display property which is used to active -webkit-line-clamp property (or find a CSS alternative to display the only two first lines as now without use the line-clamp and display properties which will permit to do works the first-letter property).
h2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
width: 75px;
border: 1px solid lightgray;
}
h2::first-letter {
color: red;
}
<h2>This is a pretty simple test</h2>
To be accurate, ::first-letter only works for:
display: block;
display: inline-block;
display: flow-root;
So if you want a result that fit you, you should use js Color JS based on this subject
DEMO:
(function() {
// Let's get all user names, you might have another structure
var users = document.querySelectorAll('h2');
// Create the span containing the highlighted asterisk
var asterisk = document.createElement('span');
asterisk.className = 'highlight';
// Walk the users (teehee) and check if the first characer is an asterisk
for (var i = 0; i < users.length; ++i) {
var user = users[i];
var text = user.textContent;
var firstLetter = text[0];
asterisk.appendChild(document.createTextNode(firstLetter));
user.removeChild(user.firstChild);
user.appendChild(asterisk);
user.appendChild(document.createTextNode(text.slice(1)));
}
})();
h2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
width: 75px;
border: 1px solid lightgray;
}
/*h2::first-letter {
color: red;
}*/
.highlight {
color: #f00;
}
<h2>This is a pretty simple test</h2>

Why overflow-y is not working as expected

I'm trying to have a scrollbar on Y axis when text gets to the end of the right side of the div.
The problem is that when I have texts with spaces its working great but when I'm entering long text without spaces the text is overlapping to X axis.
This is how it's looks:
I'm working with styled components.
This is code samples:
// The container of the white box
const TodoContainer = styled.div`
display: flex;
`;
// The white box with text inside
const TextContainer = styled.div`
text-decoration: ${(props) => props.completed && "line-through"};
background: #fff;
color: ${(props) => props.theme.palette.text.primary};
font-weight: 500;
font-size: 1.6rem;
border-radius: 5px;
padding: 1rem;
margin-right: 1rem;
overflow-x: hidden;
overflow-y: auto;
min-width: 20rem;
max-width: 20rem;
height: 2rem;
`;
You would have to tell css to break long words into new line if needed.
Just add word-break: break-all.
const TextContainer = styled.div`
word-break: break-all;
...
`;
Note: You could also use overflow-wrap: break-word. Thanks #trixn

React - Bootstrap - Making columns all the same height

Here is a fiddle showing my issue:
https://jsfiddle.net/dbfev23h/
In essence, I have a React-Bootstrap <Row> tag and underneath it I am mapping over a list of books that I want rendered as "cards" to the screen. However, because of the size of the content, the heights of the "cards" are all over the place. How do I make the height of the cards to be uniformly the height of the max height of the card in that row?
One thing that may be an issue is that there is only one bootstrap <Row>, is that the issue?
In any case, I've tried the suggestions from stuff like: https://scotch.io/bar-talk/different-tricks-on-how-to-make-bootstrap-columns-all-the-same-height and Bootstrap 4 Cards of same height in columns and Make ReactStrap/Bootstrap4 Cards In Separate Columns Same Height but nothing works.
Here is my actual code:
export class BooksComponent extends React.PureComponent {
render() {
return (
<Row>
this.props.books.map((b, index) => (
<BookComponent
key={index}
title={b.title}
summary={b.summary}
/>
))
</Row>
);
}
}
export class BookComponent extends React.PureComponent {
render() {
return (
<Col md={4}>
<div className="book-item">
<div className="book-title">{this.props.title}</div>
<div className="book-summary">{this.props.summary}</div>
</div>
</Col>
);
}
}
SCSS
.book-item {
margin-bottom: 20px;
padding: 10px;
border: 1px solid gray;
.book-title {
font-size: 1.2rem;
font-weight: bold;
}
.book-summary {
font-size: 0.9rem;
}
&:hover {
cursor: pointer;
box-shadow: 1px 1px 5px gray;
}
}
You need to make your .col-md-4 as flex because then only you will be able to stretch the child elements.
.col-md-4 {
flex: 0 0 33.3333333%;
max-width: 33.3333333%;
position: relative;
width: 100%;
padding-right: 15px;
padding-left: 15px;
display: flex;
}

Resources