Reputation: 3488
Simple enough question: I am trying to create inputs that are as large as the text supplied to them.
Sandbox: https://codesandbox.io/s/long-snowflake-6u13n?file=/src/Test.jsx
My design intention is to generate inputs dynamically and then allow the user to have styles specific to each input that visually help break up each sentence based on outside events. But before I can move forward, it's really important that my input container is only as large as the text within.
why not use a textarea? -- I have data that is particular to each sentence that I want to create unique styles for.
Any thoughts?
Upvotes: 7
Views: 16539
Reputation: 11
use sx
for overwrite css class
<TextField
sx={{
'& .MuiInputBase-root': {
color: 'white',
paddingTop: '10px'
},
'& .css-uhyr2s-MuiInputBase-root-MuiInput-root::before': {
border: '0px'
},
'& .css-uhyr2s-MuiInputBase-root-MuiInput-root:hover:not(.Mui-disabled, .Mui-error):before': {
border: '0px'
},
'& .css-uhyr2s-MuiInputBase-root-MuiInput-root::after': {
border: '0px'
},
'& textarea:focus': {
boxShadow: '0px 0px'
}
}}
/>
Upvotes: 0
Reputation: 121
The <input>
size
attribute can be used to set the width. It works in the same way as ch units e.g. <input size="10">
has a width of 10ch
. As with ch
units, it works perfectly with monospaced fonts but is only an approximation when it comes to proportionally-spaced fonts.
To get a more accurate measurement with proportionally-spaced fonts, you can use the scrollWidth
property of the <input>
element itself to calculate the width. The trick is to set the value to 'auto' first to capture the content width before setting the value. Set the input padding
to zero if you want to fit the content exactly.
import React, { useEffect, useRef } from "react";
const data = [
{ sentence: "Hello world, how are you?" },
{ sentence: "Sometimes in rains in the summer." },
{
sentence:
"Depending where you currently live, it my even rain in the winter."
}
];
const SentenceInput = (props) => {
const { value, onChange } = props;
const inputRef = useRef(null);
useEffect(() => {
const input = inputRef.current;
input.style.width = 'auto';
input.style.width = `${input.scrollWidth}px`;
});
return <input ref={inputRef} type="text" value={value} onChange={onChange} />;
};
const Test = () => {
const handleChange = () => {};
return (
<div className="App">
{data.map(({ sentence }, i) => {
return (
<SentenceInput key={i} value={sentence} onChange={handleChange} />
);
})}
</div>
);
};
export default Test;
Upvotes: 3
Reputation: 105843
Here is an approach from plain HTML/CSS and a working snippet , hidding the value typed inside a span
behind the input
set in an absolute
position
. CSS can make both span
and input
matching the same lenght/width. Stretching/collapsing a parent (label
) will finish the job.
In the courtesy of @silvenon you may also find a react sample below the snippet
var val = document.querySelector('#test');
let tpl = document.querySelector('#tpl');
let text = val.value;
tpl.textContent= text;
val.addEventListener("input", function () {// onchange ...
let text= val.value;
//console.log(text);
tpl.textContent= text;
});
label {
display: inline-block;
position: relative;
min-width: 2em;
min-height: 1.4em;
}
#tpl {
white-space: pre;
/* max-width : could be wised to set a maximum width and overflow:hidden; */
}
#test {
font-family: inherit;
font-size: inherit;
position: absolute;
vertical-align: top;
top: 0;
left: 0;
width: 100%;
background: white;
}
<label><span id="tpl"></span><input id='test' value="Some test to try" ></label>
In the courtesy of @silvenon, you may find a react sample of that code.
const SentenceInput = styled.input`
padding: 0;
margin: 0;
border: none;
border: 1px solid black;
/* added styles */
font-family: inherit;
font-size: inherit;
position: absolute;
vertical-align: top;
top: 0;
left: 0;
width: 100%;
background: white;
`
const Label = styled.label`
display: inline-block;
position: relative;
min-width: 2em;
min-height: 1.4em;
`
const Template = styled.span`
white-space: pre;
/* max-width : could be wised to set a maximum width and overflow:hidden; */
`
const Sentence = ({ initialValue }) => {
const [value, setValue] = React.useState(initialValue)
return (
<Label>
<Template>{value}</Template>
<SentenceInput
type="text"
value={value}
onChange={(event) => {
setValue(event.target.value)
}}
/>
</Label>
)
}
Upvotes: 7
Reputation: 2197
Using ch
unit would work if the typeface was monospace, otherwise character width varies. I would approach this problem by rendering an inline element holding the same text, measuring it and hiding it instantly every time the input field value changes.
To do this it's best to create a separate component for rendering sentences, let's call it Sentence
:
const Test = () => {
return (
<div className="App">
{value.map(({ sentence }, i) => {
return (
<Sentence
initialValue={sentence}
key={i}
/>
);
})}
</div>
);
};
Test
would pass the initial value, then Sentence
will continue maintaining its own state:
const Sentence = ({ initialValue }) => {
const [value, setValue] = React.useState(initialValue)
return (
<SentenceInput
type="text"
value={value}
onChange={(event) => {
setValue(event.target.value)
}}
/>
)
}
Next, I'd add a span
element that will serve as a measurer element, where the text should be styled the same way as in input elements, so the measurements turn out accurate. In your example in Chrome that would mean setting the font size to 13.3333px
.
Now for the trickiest part, we need to combine useEffect
and useLayoutEffect
; useEffect
will make the measurer visible, then useLayoutEffect
will measure it and hide it
This is the result:
const Sentence = ({ initialValue }) => {
const [value, setValue] = React.useState(initialValue)
const [visible, setVisible] = React.useState(false)
const [width, setWidth] = React.useState('auto')
const measurer = React.useRef(null)
React.useEffect(() => {
setVisible(true)
}, [value])
React.useLayoutEffect(() => {
if (visible) {
const rect = measurer.current.getBoundingClientRect()
setWidth(rect.width)
setVisible(false)
}
}, [visible])
return (
<>
<span
ref={measurer}
style={{ fontSize: '13.3333px' }}
>
{visible && value}
</span>
<SentenceInput
type="text"
value={value}
style={{ width: width + 1 }}
onChange={(event) => {
setValue(event.target.value)
}}
/>
</>
)
}
I added 1px
to the computed width
because it seems to remove a small horizontal scroll in the input fields.
This is for you to tweak further the way you want, for example how it should behave when it reaches the viewport width.
Upvotes: 2