Can Material-UI TextField width be set to match width of input text? - css

Is it possible to have Material-UI automatically adjust the width of the TextField element to match the width of the input text?
I am creating a form view/edit page and I'm rendering the data back into the same fields, however there is also a series of parameters which the server sets. It would be nice to render in disabled form elements and have their width automatically fit.
I've played with the width properly of both TextField and the underlying Input with no success. I could potentially count the characters and set a width in JS, but I'd rather a CSS solution.
<TextField
disabled={true}
label={"UUID"}
value={"7be093a5647d41ff8d958928b63d11f5"}
style={{width: "auto"}}
InputProps={{
style: {width: "100%"}
}}
/>
https://codesandbox.io/s/material-demo-forked-c3llv

You could base the width of the input on the length of the text
const FONT_SIZE = 9
const DEFAULT_INPUT_WIDTH = 200
const [textValue, setTextValue] = useState("")
const [inputWidth, setInputWidth] = useState(DEFAULT_INPUT_WIDTH)
useEffect(() => {
if (textValue.length * FONT_SIZE > DEFAULT_INPUT_WIDTH) {
setInputWidth((textValue.length + 1) * FONT_SIZE)
} else {
setInputWidth(DEFAULT_INPUT_WIDTH)
}
}, [textValue])
return (
<div>
<TextField
label={"UUID"}
value={textValue}
onChange={(e) => setTextValue(e.target.value)}
InputProps={{
style: { width: `${inputWidth}px` },
}}
/>
</div>
)
Below is the forked codesandbox
Reference: this answer

Related

Changing react-leaflet map width with button click

I am working on a map with react-leaflet. I placed a button on the map that will ideally open a menu on the left side of the map. The way I want to open up the menu is by changing the width of the map from 100% to 80%. The menu button toggles a boolean, which ideally the map will rerender and resize when the button clicks.
this is the code in my App.js
export default function App() {
const [isMarkerClick, setIsMarkerClick] = React.useState(false);
const [isMenuOn, setIsMenuOn] = React.useState(false);
function toggleMarker() {
setIsMarkerClick(prevClick => !prevClick);
}
function toggleMenu() {
setIsMenuOn(prevMenu => !prevMenu);
}
return (
<div>
<MainMap isMenuOn={isMenuOn} />
<MarkerButton isMarkerClick={isMarkerClick} toggleMarker={toggleMarker} />
<MenuButton toggleMenu={toggleMenu} />
</div>
)
}
this is where the Map Code lives
export default function MainMap(props) {
const isMenuOn = props.isMenuOn;
const isMarkerClick = props.isMarkerClick;
const zoom = 15;
const position = ['39.9526', '-75.1652'];
const [marker, setMarker] = React.useState(["", ""])
return (
<div>
<MapContainer
center={position}
zoom={zoom}
zoomControl={false}
style={{
height: "100vh",
width: isMenuOn ? "80vw" : "100vw"
}}
>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={marker}></Marker>
{isMarkerClick && <ClickTrack setMarker={setMarker} />}
</MapContainer>
</div>
)
}
as of now, when I click the menu button, the isMenuOn state updates and which then feeds back into the MapContainer and should rerender with a new width but it doesn't. Any Ideas on how to get change the map size with a click of button using react?
example of the menu button
I thought that when clicking the menu button and changing the state of isMenuOn to "true", the mapcontainer would be listening and rerender with using the new width. Although it seems like setting the width through style ={{}}, might not allow for changes?
React Leaflet will only set the width and height when the component mounts and does not update it with the state changes, in order to update the map's width you need to re-render the component fully and to do that set the component's key to something that will change with the isMenuOpen value
here is an example
<MapContainer
center={position}
zoom={zoom}
zoomControl={false}
key={isMenuOn ? "open-state": "closed-state"}
style={{
height: "100vh",
width: isMenuOn ? "80vw" : "100vw"
}}
>
The accepted answer works, but I suspect it would cause a full map reload. You can try keeping the width value static in the MapContainer element, and instead change it the wrapping div like this:
<div style={{ width: isMenuOn ? '80vw' : '100vw' }}>
<MapContainer
center={position}
zoom={zoom}
zoomControl={false}
style={{
height: '100vh',
width: '100%',
}}
/>
</div>
There is a caveat though; the map needs to be informed of the change in size, otherwise it will seem buggy. I suspect that's why the developers don't update the style after the map mount.
You can however add a map child that monitors the isMenuOn prop (or any other prop that changes your layout), and let the map instance know they should invalidate everything about sizing:
function LayoutListener({ isMenuOn }) {
const map = useMap();
useEffect(() => {
map?.invalidateSize();
}, [map, isMenuOn]);
return null;
}
And then just put in the MapContainer children:
<MapContainer>
<LayoutListener isMenuOn={isMenuOn} />
{otherChildren}
</MapContainer>

Material-UI text field with dynamic rows

I'm trying to have a multiline TextField component that takes all available space depending on a device.
I know fullWidth but is there a way to have something like fullHeight in rows setting, depending on a device that it is displayed on?
You basically want your TextField element to take full height and width of the container.
For width you can simply add fullWidth prop to your TextField element,
<TextField fullwidth/>
For adding required height,
If you have prop Multiline={true} in your TextField element, Material Ui sets numbers of rows dynamically and height is adjusted according to number of row (everytime user hits enter new row is generated), due to this you can not set specific height.
Now to be able to set height manually, you must add prop rows={1}*
<Textfield multiline rows={1} fullwidth />
Now, you should be able to set height with JSS,
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles(() => ({
inputMultiline : {
"& .MuiInputBase-input" : {
height : '100vh', //here add height of your container
},
}
}));
Now simply add this to className of your TextField element,
<TextField multiline fullWidth row={1} className={classes.inputMultiline} />
For any doubt refer to,
material ui TextField API here and
material ui styles API here.
(although, no proper information regarding this is available in docs)
A solution is to manupulate Material-UI classes using JSS.
I change the height of the input base and align the text at start vertically.
const useStyles = makeStyles(() => ({
input: {
height: "100%",
"& .MuiInputBase-root": {
height: "100%",
display: "flex",
alignItems: "start"
}
}
}));
Add the input classes to your input and it's work.
All the exemple bellow on this codesandbox.

ReactJS App: Is it possible to order the rendering of specific divs?

I'm trying to create a ReactJS (using react bootstrap) mobile app that can resize (expand or contract) itself based on the screensize. The dimensions of one part of the app need to be calculated based on how much space is left on the screen after all other parts have rendered.
For example, consider the below markup -
var calcWidth = (100 / tableSize).toString() + '%';
return(
<Container>
<Row id='1'>Header and other static stuff here</Row>
<Row id='2'>
//A db driven table with square shaped cells goes here. It has the below structure -
<Container style={{width:'100%'}}>
<Row><Col style={{width:calcWidth, paddingBottom:calcWidth}}></Col>...</Row>
...
</Container>
</Row>
<Row id='3'>Footer and other static stuff here</Row>
</Container>
);
In the above markup, Row id 1 and 3 contain static stuff like headers, footers, buttons, titles etc. The Row id 2 contains a table that can contain "n" number of cells and each cell needs to be square shaped with content horizontally and vertically centerd.
The above code correctly calculates the width of each cell from the width of the container (which is 100%) and creates square shaped cells and fits perfectly horizontally. But since the height is same as the width, it gets bigger vertically and drives the footer beyond the screen. We want to avoid scrollbars. The solution seems to be to calculate calcWidth based on the remaining height available to the table, something like the below -
var remainingHeight = <total height of the container> - <height taken up by Row 1> - <height taken up by Row 3>
var width = <width of the screen>
var calcWidth = ((remainingHeight < width ? remainingHeight : width) / tableSize).toString() + '%';
My questions are -
How to calculate the remainingHeight variable above? How to let Row1 and Row3 render before Row2 and then calculate the remaining height?
How to find the total height and width of the container?
Any other better way of doing this? I'm just a newbie, probably there are some css tools to do it more efficiently?
Here you can find an example on how to calculate the height of react components after rendering:
export default function App() {
const [height1, setHeigt1] = useState(0);
const [height2, setHeight2] = useState(0);
const [height3, setHeight3] = useState(0);
const [remainingHeight, setRemainingHeight] = useState(0);
useEffect(() => {
const remainingHeight = 100 - height1 - height2 - height3;
console.log(remainingHeight);
setRemainingHeight(remainingHeight);
}, [setRemainingHeight, height1, height2, height3]);
return (
<div
id="container"
style={{
height: "100px",
backgroundColor: "firebrick",
padding: "15px"
}}
>
<ResizableComponent
id="component-1"
content={`Initial component 1 height = ${height1}`}
onHeightUpdated={setHeigt1}
/>
<ResizableComponent
id="component-2"
content={`Initial component 2 height = ${height2}`}
onHeightUpdated={setHeight2}
/>
<ResizableComponent
id="component-3"
content={`Initial component 3 height = ${height3}`}
onHeightUpdated={setHeight3}
remainingHeight={remainingHeight}
/>
</div>
);
}
export function ResizableComponent({
id,
content,
onHeightUpdated,
remainingHeight
}) {
const [height, setHeight] = useState(0);
const [isFirstRender, setIsFirstRender] = useState(true);
useEffect(() => {
const newHeight = document.getElementById(id).clientHeight;
if (height !== newHeight && isFirstRender) {
setHeight(newHeight);
setIsFirstRender(false);
}
}, [isFirstRender, id, height, onHeightUpdated, remainingHeight]);
useEffect(() => {
onHeightUpdated(height);
}, [height, onHeightUpdated]);
return (
<div
id={id}
style={
remainingHeight
? {
backgroundColor: "pink",
height: `calc(${height}px + ${remainingHeight}px)`
}
: { backgroundColor: "pink" }
}
>
{content}
</div>
);
}

How to make non-interactive Grid overlay?

I want to have the Grid visible on my site while i'm developing it, a-la Adobe XD style (link to image), to better understand how much place elements are taking(or should take).
The code underneath works but I have to manually specify Width of the grid when setting position to 'fixed' or 'absolute'.
There must be a more elegant way to do this.
Any suggestions on how to do this?
const testGridItems = () =>{
var list = new Array();
for(let i=0; i<12; i++){
list.push(
<Grid item xs={1}>
<div align='center'
style={{
height: '100vh',
position:'relative',
backgroundColor: '#F6BB42',
opacity:0.2}
}>
{i+1}
</div>
</Grid>
)
}
return list;
}
const testGrid = () => {
return (
<Grid container
style={{
width:1200,
position:'fixed'
}}
spacing={24} >
{ testGridItems() }
</Grid>
)
}

How to make Material UI TextField less wide when in Table

I've got a Material UI Table.
I build it like this:
tableValues.map((curRow, row) => {
tableRows.push(
<TableRow key={this.state.key + "_row_" + row}>
{curRow.map((cellContent, col) => {
let adHocProps = {...this.props, type:"Text", label:"", value:cellContent}
return (
<TableCell className={classes.tableCell} key={this.props.key + "_row:" + row + "_col:" + col}>
{col===0 && this.props.rowHeaders ?
<div className={classes.header}>{cellContent}</div> :
<Question {...adHocProps} stateChangeHandler={this.handleTableChange("in build")} />}
</TableCell>
)})}
</TableRow>
);
return null;
});
return (
<Table key={this.props.key + "_table"} className={classes.table}>
<TableHead>
<TableRow>
{this.props.colHeaders.map((header) => <TableCell className={classes.tableCell} key={this.props.id + header}><div className={classes.header}>{header}</div></TableCell>)}
</TableRow>
</TableHead>
<TableBody>
{tableRows}
</TableBody>
</Table>
);
The Question is actually a glorified [TextField]2 created thusly:
<div>
<TextField
value={this.state.value}
onChange={this.handleTextChange(this.props.key)}
key={this.props.key}
id={this.props.id}
label={this.props.label}
placeholder={realPlaceholder}
className={classes.textField}
fullWidth
xmlvalue={this.props.XMLValue}
/>
</div>
... and then wrapped in Paper.
The styles are:
tableCell: {
padding: 5,
},
textField: {
padding: 0,
margin: 0,
backgroundColor: "#191",
}
This works, I get the appropriate content in each cell.... but the Question element is way wider than needed, and appear to have a min width and some padding I can't remove.
The table is full-width until you get to a certain point, then notice here:
that when the window is shrunk below a certain level, the table doesn't shrink any further. Acting as if the elements inside have a minimum width.
As a process of investigation, I change the Question element to simply return "Hi". When it does, the table then looks like this:
(which is to say, it condenses nicely... still too much padding on the tops and bottom and right, but WAY better)
So that leads me to believe the issue is with my Question component. I should note this happens on other Questions as well -- they all appear to have a min width when a width is not defined for them... UNLESS they are placed inside a container that has a designated width such as a Material UI Grid. For example, when placed in a `Grid and the window is shrunk, they shrink appropriately:
So why isn't the Table/TableCell also shrinking the TextField like the Grid does? (or: how do I remove the apparent "MinWidth" on my textFields?) Do Material UI TextFields have a minimum width if one isn't otherwise specified?
For what it's worth, I have tried specifying the column widths of the table -- with success when the table is wide, but it still doesn't solve the apparent minimum width issue.
I have also tried changing the Question component to <input type="text" name="fname" /> and still have the same problem. It's interesting that that the Question component is simply "hi" the problem disappears but that when it's an input, it shows up.
I have discovered that the native input fields default width is 20 characters: https://www.w3schools.com/tags/att_input_size.asp
The 'size' property is key here:
Specifies the width of an element, in characters. Default
value is 20
To set the width of the TextField, you must pass properties to the native input field.
If you wish to alter the properties applied to the native input, you
can do so as follows:
const inputProps = {
step: 300,
};
return <TextField id="time" type="time" inputProps={inputProps} />;
For my use case, the following modified the sizes of the TextFields to be 10 characters in size:
<TextField
value={this.state.value}
onChange={this.handleTextChange(this.props.key)}
key={this.props.key}
id={this.props.id}
label={this.props.label}
placeholder={realPlaceholder}
className={classes.textField}
fullWidth
xmlvalue={this.props.XMLValue}
inputProps={{
size: 10
}}
/>
Unfortunately, this is a bit squishy... it neither holds the input field at exactly size nor does it treat it like a minimum size.... There appears to be some heirarchy of sizing in play between GridItems, table Columns, free-flow flex areas, and the actual TextField elements... and I'm not well versed enough to know what always 'wins'.

Resources