0

I have a list of buttons that uses roman numerals as label, something like this:

{list.map((item, index) => {
  return (
    <li key={`${item.id}`}>
      <button
        onClick={() => handleChange(index)}
      >
        {transformNumberToRomanNumeral(index + 1)}
      </button>
    </li>
  )
})}

because of that, the numerals will have different sizes, like this: "I", "II", "III".

I need to make all the buttons the same size, both width and height, button if I set the width to a fixed value, some text will still overflow the button width. Is there a way to make the text scale to fit the max width, but never overflow it?

I tried using using svg with text tag, but the scale looks really bad, it usually don't get all the height so the text end ups really small.

Sku
  • 163
  • 2
  • 14
  • 1
    What determines the button dimensions? (I mean, why can't they be wide enough to accommodate the longest roman numeral you will have?). – A Haworth Jan 15 '22 at 21:54
  • @AHaworth in some places I'm using an carousel with that component, the buttons look really ugly and out of the UI layout when they start to get big inside that carousel. I tested making the font smaller, and it does help, but I wanted that to be auto behavior, so I only have to define the size of the container, since the componente is used in a bunch of places with different container sizes – Sku Jan 15 '22 at 22:16
  • So, within say one particular container (e.g. a carousel) you need all the Roman numerals to have the same font size and for that font size to be small enough so every numeral fits within its button. Is that correct or is the font size to be adjusted only when that particular Roman numeral would not otherwise fit? – A Haworth Jan 16 '22 at 07:31
  • @AHaworth I don't really need the fotn-size to be the same. I want it to have the biggest font size possible based on the container size (for examplle a button with 50px width), and if the text would overflow the container, I want the font to scale to a small size, so it will fit the container – Sku Jan 16 '22 at 17:36
  • What's the maximum number of characters you will need? MDCCCLXXXVIII (1888) is 13 characters long. Will you need more than that? – Rounin Jan 16 '22 at 20:22
  • @Rounin, No, 13 is more than enough – Sku Jan 16 '22 at 22:02

3 Answers3

1

In the example below, the buttons have a consistent width of 50px.

Where n is the number of characters in the roman numeral, both the:

  • font-size
  • line-height

are: (14 - n)

except where (14 - n) < 6, in which case that value remains 6.


Working Example:

let buttons = document.querySelectorAll('button');

for (let button of buttons) {

  const numeralCount = button.textContent.length;
  let textSize = (14 - numeralCount);
  textSize = (textSize < 6) ? 6 : textSize;
  button.style.setProperty('--text-size', textSize + 'px');
}
:root {
  --text-size: 6px;
}

button {
  display: block;
  width: 50px;
  height: 18px;
  margin: 6px 0 0;
  padding: 0 2px;
  line-height: var(--text-size);
  font-size: var(--text-size);
  overflow-x: hidden;
}
<ol>
<li><button>M</button></li>
<li><button>MD</button></li>
<li><button>MDC</button></li>
<li><button>MDCC</button></li>
<li><button>MDCCC</button></li>
<li><button>MDCCCL</button></li>
<li><button>MDCCCLX</button></li>
<li><button>MDCCCLXX</button></li>
<li><button>MDCCCLXXX</button></li>
<li><button>MDCCCLXXXV</button></li>
<li><button>MDCCCLXXXVI</button></li>
<li><button>MDCCCLXXXVII</button></li>
<li><button>MDCCCLXXXVIII</button></li>
</ol>
Rounin
  • 27,134
  • 9
  • 83
  • 108
0

I managed to do something that seems to look like what you want. Here is the Stackblitz and here is the code :

import React from 'react';
import { render } from 'react-dom';
import './style.css';

const App = () => {
  return (
    <div className="container">
      <button>
        <SvgRenderer text="VII" />
      </button>
      <button>
        <SvgRenderer text="LXXIII" />
      </button>
      <button>
        <SvgRenderer text="LXIII" />
      </button>
      <button>
        <SvgRenderer text="LXXIII" />
      </button>
      <button>
        <SvgRenderer text="LXXXIII" />
      </button>
    </div>
  );
};

const SvgRenderer = ({ text }) => (
  <svg
    width="100%"
    height="100%"
    viewBox="0 0 100 50"
    preserveAspectRatio="xMinYMid meet"
    // style={{ backgroundColor: 'green' }}
  >
    <text
      x="0"
      y="45"
      fill="black"
      lengthAdjust="spacingAndGlyphs"
      textLength="100"
    >
      {text}
    </text>
  </svg>
);

render(<App />, document.getElementById('root'));

I based this on this post. Since I couldn't manage to make the checked answer to work properly, I used one of the answer bellow, but note that I'm absolutely not a pro svg so there might be a way better answer than mine using SVG (or other answers).


[previous answer] You coud do this by using flexbox. This way you don't need to set with and height. Here is the Stackblitz and here is the code :

import React from 'react';
import { render } from 'react-dom';
import './style.css';

const App = () => {
  return (
    <div className="container">
      <button>some text</button>
      <button>Some more text</button>
      <button>Some more text longer</button>
      <button>Some more text longer again and again</button>
      <button>Some more text longer again and again, and again ?</button>
    </div>
  );
};

render(<App />, document.getElementById('root'));

And some css :

.container {
  display: flex;
  flex-wrap: wrap
}

.container > button {
  flex: 1
}

I used long string in the buttons for the use case, but with roman numerals it should be ok I guess.

Quentin Grisel
  • 4,794
  • 1
  • 10
  • 15
  • thanks for the help, but this doens`t fix my problem. I edited your code to be simillar to my problem: https://stackblitz.com/edit/react-functional-boilerplate-16-13-1-rqxrqz?file=style.css. Im looking for way to always adjust the font to the container, something like fittext.js but without libs, just js or react – Sku Jan 16 '22 at 05:24
  • @Sku Thx for the repro. I edited my post to add something that seems to answer your needs. – Quentin Grisel Jan 16 '22 at 10:23
0

You can iterate through the buttons, using a dummy button to notice when the text just fits within the button width.

This snippet starts with a largeish font size assuming the button width is 50px and gradually brings the font size down until the text fits.

<style>
.container {
  widht: 100vw;
  display: flex;
}
button {
  width: 50px;
  height: 50px;
  padding: 0px;
  text-align: center;
  clear: both;
}
</style>
<body>
<div class="container">
  <button>I</button>
  <button>II</button>
  <button>III</button>
  <button>VIII</button>
  <button>MMCXXII</button>
</div>
</body>
<script>
const buttonW = 50;
const buttons = document.querySelectorAll('button');
const dummy = document.createElement('button');
let w, fs ;
document.body.appendChild(dummy);
dummy.style.padding = 0;
buttons.forEach( button => {
  fs = 40;
  dummy.style.width = 'auto';
  dummy.style.fontSize = fs + 'px';
  dummy.innerHTML = button.innerHTML;
  w = window.getComputedStyle(dummy).width.replace('px', '');
  while  (w > buttonW) {
    w = window.getComputedStyle(dummy).width.replace('px', '');
    fs--;
    dummy.style.fontSize = fs + 'px';
  }
  button.style.fontSize = fs + 'px';
});
dummy.remove();
</script>
A Haworth
  • 30,908
  • 4
  • 11
  • 14