Reducing the size of an internal label within a flex container

Hello everyone, I could really use some assistance here. The problem I'm facing involves a flex-container with tabs (the number of tabs can vary as they are passed as an array in a React component).

Each tab consists of a label and a span with a number. According to the initial conditions for this task, the label should not be shorter than 3 letters + "..." (we use ellipsis to indicate this).

From what I understand, the only solution is to manually code this (as the ch unit is based on the '0' symbol, leading to inaccuracies). But now, onto the main issue. The text in the label can vary in length, and we can have a different number of tabs.

I need to arrange the tabs in the container (which is restricted by a max-width of 900px) as efficiently as possible. What this means is that we set the tabs with the full length of the label if it's possible, if not - the label shrinks until it reaches the min-width (6ch). If the number of tabs is too large (where all labels have reached min-width but the tabs exceed the container), I won't display them at all. I plan to implement this using useLayoutEffect with checks for exceeding the container.

The main problem currently is that spans overflow the tabs, meaning that labels have the ability to shrink, but instead, other tabs start shrinking and this causes issues with the span. I've tried using a grid with template columns of 1fr width (where the number of columns can be set by passing the array length to a styled component). While this works, I need the tabs to be aligned to the left side (instead of taking up all available space) and I'm experiencing problems with extra empty space if label + gap + span < 1fr of the container.

At this moment, I don't have a solution other than hardcoding the min-width of the tab, but we all understand that this is unacceptable (especially considering there could be 10,000, for example, in the span). I humbly request assistance. Finding a solution would make me the happiest person.

I have included images demonstrating the issue, code snippets, and a link to the CodeSandbox example (where you can insert tabs in the mock_data and change the word lengths). 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 decision: hardcode min-width
// of tab ~ 88px (53px for the first - it will always be "all")

Answer №1

I managed to achieve the desired behavior by utilizing grid instead of flexbox. I dynamically set the number of columns on the parent container and gave them a width of minmax(min-content, max-content) (except for the first column which always contains the element - All). The separate tab was transformed into a 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>
      )}
    </>
  );
};

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Storing Node_modules in a separate directory

All my node modules are located in the path C:/Users/user/AppData/Roaming/npm/node_modules. I am attempting to include these node modules with babel and babel-presets for my webpack scripts. Here is my webpack.config.js module.exports = { context: &a ...

Having trouble rendering the map on my React page

Struggling to display a list of objects in a React component. The data is fetched correctly and logged to the console, but for some reason, the object names are not rendering in a list on the screen when the page reloads. Can't figure out where the er ...

Avoiding the selection of HTML canvas objects

I am currently working on customizing my homepage with an interactive animation. However, I am facing some challenges in integrating it seamlessly into the page. You can view the progress at . My main issue is preventing the canvas object from being select ...

Embed a physical entity within another physical entity

On my webpage, I have 2 toggle buttons - "Leaderboard" and "MedalTally". The layout looks like this: https://i.sstatic.net/IohqA.png Here are the codes for the above page: *, *:before, *:after { box-sizing: border-box; } html { overflow-y: scrol ...

How come the transition does not take effect when removing and adding the class to the same element with the removeClass() and addClass() methods?

Two images are present, with the first one having the class "opacityOne". When a button is clicked, based on the variable index, I want the current image to fade in while the other fades out. It works well when I remove the "opacityOne" class from one ima ...

Unexpected malfunction of Wordpress modals and JavaScript stopped abruptly

I previously had a website built with pure JS and HTML, but decided to transfer it to WordPress. Everything was functioning properly until a few days ago when two of my functions suddenly stopped working. Both functions are supposed to add an "active" clas ...

Tips for positioning a grid at the center of a MaterialUI layout

I am struggling to position 3 paper elements in the center of both the vertical and horizontal axes on the screen. Despite applying various CSS rules and properties, the height of the HTML element consistently shows as 76 pixels when inspected in the con ...

Tips for minimizing the height of the Material Toolbar in Material-UI

I'm looking to customize the height of the toolbar in Material-UI to make it smaller Although I checked out How do I change the Material UI Toolbar height?, I am still struggling with this issue Increasing the height above 50 seems to work, but redu ...

Exploring the Challenges of Dynamic Routing in ReactJS

I have a set of components that I need to convert into dynamic URLs. When accessing the browser, for example, http://localhost:3000/houses/1, I want it to display House 1. Everything else in the application is functioning correctly. I just need to resolve ...

Guide to retrieving objects in React.js

Struggling to extract a country from an online JSON file that I am currently fetching. I am attempting to streamline the process by creating a function to retrieve the country from the dataset and avoid repeating code. However, I am encountering difficulti ...

Refresh the page before the conclusion of the express-Node js function

I am encountering an issue with a function that functions properly with small files but fails when dealing with large files. The problem occurs when the axios post request in Express JS ends, causing a page refresh. I am using React JS on the client side a ...

What is the best way to animate an element when it comes into the user's view

In order to activate the animation of the skill-bars when the element is displayed on the website, I am seeking a solution where scrolling down through the section triggers the animation. Although I have managed to conceptualize and implement the idea with ...

What is the best way to implement a personalized hook in React that will return the number of times a specific key is pressed?

I'm working on a custom hook that should give me the key pressed, but I've noticed that when I press the same key more than twice, it only registers twice. Here's my code: import { useEffect, useState } from "react" function useKe ...

The data stored in LocalStorage disappears when the page is refreshed

I'm facing an issue with the getItem method in my localStorage within my React Form. I have added an onChange attribute: <div className = 'InputForm' onChange={save_data}> I have found the setItem function to save the data. Here is ...

Error: The AppwriteException occurred because the user with the role of guests does not have access to the account scope. Attempting to access the following URL resulted in a 401 (

Hey everyone, I hope you're all doing great. I've encountered an issue with the appwrite service and here is the code snippet: client = new Client(); account; constructor(){ this.client.setEndpoint(String(import.meta.env.VITE_APP ...

Adjustable pseudo-elements derived from the size of the element

I have a unique feature in my web application that involves a span element with inner text and additional :before and :after pseudo-elements used as decorative lines on each side of the text. Check out this JSFiddle example to see exactly what I mean. .c ...

What is the best way to import external images into Next.js?

I'm currently working with JavaScript to load random images from , but I'm facing issues with it not functioning properly. It's worth noting that I prefer not to utilize the new image optimization in next.js at this time. While local images ...

React application fails to update the log data that is being streamed via socket.io

Currently, in my project, I am utilizing flask_socketio as the server and socket.io-client with react as the client. The main.py file (which is the flask server) reads a log file (specifically console.log) that is receiving continuous updates. Upon clickin ...

What methods can I use to prevent a number from becoming negative?

I am currently developing a game where players collect resources, spend them to create more resources, and engage in various activities. However, I'm facing an issue where the resource count goes into negative numbers when too much of a certain item i ...

What is the best way to eliminate excess space on the right side of a div element

I recently utilized a column structure with bootstrap 4, resulting in a layout with 1 row containing 2 columns: <div class="left"> <hr class="container hline"> <div id="main" class="container border"> <di ...