Responsive Margins with React - reactjs

I am currently experiencing a structural problem with React and styled-components where I cannot find a seemingly nice solution for every case.
Here's the (quite simple) problem:
<Margin top="10%" left="5%">
<Card>Some text</Card>
</Margin>
The Margin component extracts the margin settings from the Card component so that the Card component can be reused in other scenarios. The Margin component itself can be reused aswell.
If one wanted to make this responsive though, it would only be possible via different props like topPhone, topTablet and so on, which I find very unintuitive and hard to maintain if the responsive boundaries ever change.
So what one could do is duplicate all the content into different other components and display only the one that currently fits (e.g. react-responsive's MediaQuery component).
But in that case, the page would have a lot of overhead for different device sizes.
What would be the best way to approach this?

Here’s an outline of the implementation I use (slimmed down), it may fit your case. It involves a little set up but helps keep responsive boundaries maintainable (which I believe is the main concern).
1) Setup an object with your desired responsive boundaries:
const screenSize = {
sm: 576,
md: 768,
lg: 992,
};
2) Create a Theme to help pass those boundaries through your application via styled-components. Here I set them up with the breakpoints (media-queries) in mind, using something like Bootstrap as a guideline (These should be extended to include smMin, mdMin etc):
const theme = () => {
return {
xsMax: screenSize.sm - 1 + 'px',
smMax: screenSize.md - 1 + 'px',
mdMax: screenSize.lg - 1 + 'px',
};
};
3) Wrap your application with your Theme as mention here.
render() {
return (
<ThemeProvider theme={theme}>
<MyApplication />
</ThemeProvider>
)
}
4) Using styled-components’ tips-and-tricks as an example. Create an exportable media helper to make use of those Theme breakpoints (I’ll only illustrate 2 breakpoints, but you should expand this to include xsDown, smDown, lgUp, mdOnly etc etc)
export const media = {
smDown: (...args) => css`
#media (max-width: ${({ theme }) => theme.smMax}) {
${css(...args)};
}
`,
mdDown: (...args) => css`
#media (max-width: ${({ theme }) => theme.mdMax}) {
${css(...args)};
}
`,
};
5) import media and use it like a normal old media-query wrapping your responsive styling:
const MyComponent = styled('section')`
/* Everything to be pink on screens with widths < 768px */
${media.smDown`
* {
background: papayawhip;
}
`};
`;
const MyOtherComponent = styled(‘li’)`
/* Everything to be purple on screens with widths < 992px */
${media.mdDown`
* {
background: purple;
}
`};
`;
You can import media and reused it in Margin or any other component in your application. Responsive boundaries are maintained in one place.

Have you checked out react-socks yet? It helps building responsive React components.
<Breakpoint topPhone only>
<div>I will render only in topPhone</div>
</Breakpoint>
<Breakpoint topTablet only>
<div>I will render only in topTablet</div>
</Breakpoint>
Disclaimer: I am the author of this library.

Related

what's the easiest way to set a custom height for a mui chip for versions < 5.0?

I'm currently working on app that uses MUI v.4.11. I need the ability to set a custom height for a Chip. size='small' is too big and I need to specify a shorter height.
I know how to do this with v.5.0+ via the "sx" prop. But I'm currently constrained to a lower version. What would be the easiest way for me to set the Chip height given my app's current version constraint?
In v4, you can extend the css class .MuiChip-root, to add your custom height:
const useStyles = makeStyles(theme => ({
chip: {
'&.MuiChip-root': {
height: theme.spacing(0.5)
}
}));
In your react component:
const classes = useStyles();
return <Chip className={classes.chip} ... />

Dynamically increase react-table height to fill screen size

Is this possible to dynamically increase table height, based on the browser window height, when using the virtualized react-table (example that I'm working with) ?
There's lots of empty space in my browser that I wish it'll fill up, yet I don't wish the table to overflow my screen, only to go up to the end of it (with a scrollbar allowing access to the remaining rows, if any).
I tried to use this flex box css code, but it didn't do anything. Note that I've successfully used this css code for a different component and it worked.
.flexbox-container {
display: flex;
height: 100vh;
flex-direction: column;
}
.test1 {
display: flex;
flex-grow: 1;
}
I can see that the react-table example relies on FixedSizeList which seems to fix the height, which perhaps is disabling the flex box? I've tried to remove all the optional props but it didn't work.
<FixedSizeList
height={800}
itemCount={rows.length}
itemSize={35}
width={totalColumnsWidth+scrollBarSize}
>
{RenderRow}
</FixedSizeList>
When my application have multiple datas i solve with pagination
After user have a pagination hell (many-page)
i used react-window to solve problem after i confroted to your probleme
is solved to this code this sell hooks
import React from 'react'
/**
* Get small piece of screen to dynamic height table
*/
const calcHeight = (val) => (val - (Math.round(val / 4)))
export default function useHeight() {
const [height, setHeight] = React.useState(calcHeight(window.innerHeight))
const detectWindowHeight = (h) => setHeight(calcHeight(h))
React.useEffect(() => {
window.addEventListener("resize", (e) => detectWindowHeight(e.target.innerHeight))
return () => window.removeEventListener("resize", (e) => detectWindowHeight(e.target.innerHeight))
}, [])
return { wH: height }
}
you can use variable wH => to dynamic height your table
Thanks to respond me !!!

Bordercolor override in Select component material ui on error

I need to override select component with border color to orange on error. Globally the outlined input bordercolor within my application is provided green. Only on error the border color has to overridden
import { FormControl, makeStyles, Select, withStyles } from '#material-ui/core';
const useStyles = makeStyles((theme) => ({
select: {
'&:before': {
borderColor: 'red',
},
'&:after': {
borderColor: 'red',
},
},
}));
const CustomSelect = ({
id,
value,
name,
variant,
onChange,
error,
priceType,
}) => {
const classes = useStyles();
return (
<FormControl size='small' error width='154px'>
<Select
id='price-type'
value={priceType}
native
name='priceType'
variant='outlined'
onChange={onChange}
error={true}
inputProps={{
style: {
width: '154px',
},
}}
className={classes.select}
>
<option value={''}></option>
<option value={'Retail'}>Retail</option>
<option value={'X-East'}>X-East</option>
</Select>
</FormControl>
);
};
export default CustomSelect;
General approach
Usually in MaterialUI when we want to override MaterialUI styles that are applied conditionally, we may use the classes prop:
classes={{ root: <class to apply always>, error: <class to apply in error state>}}
You can read about the CSS API and which classes are applicable for a given component at the bottom of its API page.
Alternatively, we can use a more elaborate approach and simply apply a rule whenever the component is in error state:
className={ error ? <class to apply in error state> : undefined }
The Select component
The Select component seems to be written at another time than most of the other MaterialUI input components. This is my intuitive impression based on API and code style but I might be wrong. Nevertheless, the Select component uses one of Input, FilledInput or OutlinedInput depending on the value of the variant property. Since you use outlined, I will continue this answer with OutlinedInput in mind. I will also use the color green for the border instead of orange because its distinction from red is more clear.
Naively, I would tackle the problem by looking at the CSS API of the Select component, and then also look closer at the OutlinedInput to which remaining properties of the Select component are spread. No applicable CSS API class exists on the Select component to reach your goal, but the OutlinedInput has a class error with the following description:
Pseudo-class applied to the root element if error={true}.
If we inspect the OutlinedInput in the development tools, we can see that the element that has the border is a fieldset residing inside the root element of the OutlinedInput (note: not the root element of the Select). If we use the error class, we will end up with an additional class applied to the root element of the OutlinedInput which is not what we want. Nonetheless, we could apply a class that refers to the aforementioned child element... this would work.
There is also a css class notchedOutline which we can see is applied to this fieldset resulting in the border. The dilemma here is that if we would be able to add some css to this class, then it would be applied at all times on the fieldset, but we want to apply it ONLY in error state.
This gives us four possible strategies to affect the css via the CSS API:
Add css conditionally (when in error) to the root class of the underlying OutlinedInput that targets child notchedOutline.
Add css to the root class of the underlying OutlinedInput that targets error state and child notchedOutline.
Add css to the error class of the underlying OutlinedInput and target child notchedOutline.
Add css conditionally (when in error) to the notchedOutline class of the underlying OutlinedInput.
Note that the second one and third are preferable in that they do not require us to add a class conditionally; this is handled by MaterialUI.
In the best of worlds, I would want the Select component to forward all of its non-existing props to the element it uses internally, in this case an OutlinedInput, but apparently, it does not forward portions of elements that it partially supports (the classes prop). This means that if we supply classes.error or classes.notchedOutline, MaterialUI will complain with
Material-UI: The key `XXXX` provided to the classes prop is not implemented in ForwardRef(Select).
instead of forwarding the prop to the underlying OutlinedInput. I do not know if this is a bug or not, but this is how it works currently.
This implies that we can not use any of the above four strategies via the classes prop of the Select component. A workaround is to use the Select className prop which is forwarded to the OutlinedInput which in turn forwards it to InputBase which forwards it to the native element, which is the same as the root element of the OutlinedInput. So, if we could access the underlying OutlinedInput itself, we could use all of the above strategies, otherwise only the first two (and then via className prop of the Select).
The solutions
1. Set the global error color of the entire app
If you want the error color to be something else than red for the entire app, then provide a theme which changes the color used for errors. This is the best approach since it also changes the color of helper texts and labels which would otherwise have a different color than the border of your Select input.
import green from '#material-ui/core/colors/green';
const theme = createTheme({
palette: {
error: {
main: green[500]
},
},
});
This is a clean and non-hacky way to do it and the one I recommend. However, if you DON'T want the general color for errors to be changed, this might not be a good solution, even though you could wrap just a local part of the app in a theme of its own.
2. Override the class used for notchedOutline in error state
This is a broad way to solve it since you can do this in several different ways using the four strategies described above. The downsides with all the solutions are that they are more or less hacky and that they only affect the border of the Select, implying that helper texts and labels will be differently colored than the border when the component is in error state (you could add styles to them too, making all of this very elaborate and hard to achieve).
2.a Global override / default for OutlinedInput
We can create a theme with a global override for the OutlinedInput. Since we then manipulate the OutlinedInput itself, we may use all four strategies from above.
Global override / default for OutlinedInput for root class targeting notchedOutline child
This theme would have to be applied conditionally only when the component is in error state. This is elaborate and not a good alternative.
Global override / default for OutlinedInput for root class targeting error state and notchedOutline child
const themeWithOverrides = createTheme({
overrides: {
MuiOutlinedInput: {
root: {
"&$error": {
"& $notchedOutline": {
border: "1px solid green"
}
}
}
}
}
})
This one works well and targets the OutlinedInput root class with css that applies only when it is in error state and applies to its notchedOutline child. The selectors refer to the css classes already defined by default in the theme for this component.
Global override / default for OutlinedInput for error class and notchedOutline child
const themeWithOverrides = createTheme({
overrides: {
MuiOutlinedInput: {
error: {
"& $notchedOutline": {
border: "1px solid green"
}
}
}
}
})
This solution results in an error message from MaterialUI
Material-UI: The `MuiOutlinedInput` component increases the CSS specificity of the `error` internal state.
which in turn recommends us to use the previous solution targeting the root class instead.
Global override / default for OutlinedInput for notchedOutline class
const themeWithOverrides = createTheme({
overrides: {
MuiOutlinedInput: {
notchedOutline: {
border: "1px solid green"
}
}
}
})
This will apply the style at all times which we don't want, so the theme has to be applied conditionally which is a hassle so this solution is not recommended at all.
2.b Css classes for Select component
Here we could also, in theory, use all of the four strategies described above. However, since we are not manipulating the OutlinedInput itself, and since MaterialUI doesn't forward the classes props that are not defined for the Select to the underlying component, only two remains.
Adding css class conditionally to className prop of the Select which is applied to the OutlinedInput root component targeting child notchedOutline
const useStyles = makeStyles((theme) => ({
errorState: {
"& .MuiOutlinedInput-notchedOutline": {
border: "1px solid green !important"
}
}
}))
...
<Select
...
className={ errorFlag ? classes.errorState : undefined }
...
</Select>
This solution both uses literal MaterialUI css classes and requires us to conditionally add the class. Finally, we have to use the !important modifier to override the specificity of the MaterialUI default behaviour. All in all, this has a slightly hacky flavour to it.
Adding css class to className prop of the Select which is applied to the OutlinedInput root component targeting child notchedOutline only in error state
const useStyles = makeStyles((theme) => ({
errorState: {
"&.Mui-error": {
"& .MuiOutlinedInput-notchedOutline": {
border: "1px solid green"
}
}
}
}))
...
<Select
...
className={ classes.errorState }
...
</Select>
This is slightly better because we don't need to conditionally apply the style. Also, as long as this style is injected after the MaterialUI default style, we can omit the !important modifier since this rule achieves the same specificity as the MaterialUI default behaviour. This method is absolutely preferable over the previous.
2.c Css classes for underlying OutlinedInput component via input prop on Select
The Select component takes a prop input which lets you define your own input components to be used. Here we can use a custom component that returns an OutlinedInput with the modifications you want.
export default function CustomInput(props) {
return (
<OutlinedInput
{...props}
<CHANGES HERE>
/>
)
}
Since we here can work directly towards the OutlinedInput, we can use its CSS API and thus use all the four strategies described earlier.
Note that to do it by the book, you should merge incoming classes.error and classes.notchedOutline with the classes you assign by default as well.
Adding css class conditionally to classes.root prop targeting child notchedOutline
Since we now have access to the classes.error prop, providing this class conditionally would not be a smart option.
Adding css class to classes.root prop targeting state error and child notchedOutline
Similarly, since we now have access to the classes.error prop, providing a class here targeting error state instead of targeting it directly would be an extra workaround and is not recommended.
Adding css class to classes.error prop targeting child notchedOutline
const useStyles = makeStyles((theme) => ({
error: {
"& .MuiOutlinedInput-notchedOutline": {
border: "1px solid green !important"
}
}
}))
export default function CustomInput(props) {
const classes = useStyles()
return (
<OutlinedInput
{...props}
classes={{ ...props.classes, error: classes.error }}
/>
)
}
This is a good alternative that allows us to leave the decision of whether the component is in error state or not to MaterialUI. Slightly hacky to use the literal MaterialUI css classes and that we have to provide the !important flag to override the higher specificity of the MaterialUI default behaviour.
Adding css class conditionally to classes.notchedOutline prop when in error state
const useStyles = makeStyles((theme) => ({
errorOutline: {
border: "1px solid green !important"
}
}))
export default function CustomInput(props) {
const classes = useStyles()
return (
<OutlinedInput
{...props}
classes={{ ...props.classes, notchedOutline: errorFlag ? classes.errorOutline : undefined }}
/>
)
}
This is equally good as the previous. It saves us from using literal
MaterialUI css classes but it forces us to add the class conditionally. Also here we have to provide the !important flag to override the higher specificity of the MaterialUI default behaviour.
Conclusion
It's a shame that it seems to be quite complicated to change such a simple thing. Nonetheless, the best solutions seem to be:
Use global error color override if you want to have a robust solution with uniform appearence for all inputs in your app that harmonize with labels and helper texts.
import green from '#material-ui/core/colors/green';
const theme = createTheme({
palette: {
error: {
main: green[500]
},
},
})
Use global override for OutlinedInput if you want a uniform error color across all OutlinedInputs and if you're ok with labels and helper texts that don't harmonize.
const themeWithOverrides = createTheme({
overrides: {
MuiOutlinedInput: {
root: {
"&$error": {
"& $notchedOutline": {
border: "1px solid green"
}
}
}
}
}
})
Use a custom input which conditionally overrides the classes.notchedOutline when the component is in error state if you need a one-time change on a single or couple of Selects / OutlinedInputs and if you can accept labels and helper texts that don't harmonize in color. I prefer this solution because I don't like to use literal MaterialUI css classes in my code.
const useStyles = makeStyles((theme) => ({
errorOutline: {
border: "1px solid green !important"
}
}))
export default function CustomInput(props) {
const classes = useStyles()
return (
<OutlinedInput
{...props}
classes={{ ...props.classes, notchedOutline: errorFlag ? classes.errorOutline : undefined }}
/>
)
}
...
<Select input={<CustomInput error={errorFlag} />} />
...
Here is a code sandbox with some of these recommended examples
You can use this styles
const useStyles = makeStyles((theme) => ({
select: {
borderColor:'#f00',
'&:focus': {
borderColor: '#f00',
},
},
}));

Material-ui how to make the search textfield expandable in material-table

I'm using material-table and I want to make the search text field expandable like in this example. But I can't figure out how to change the style for that text field.
My table
I've tried something like this but is not working..
options={{
searchFieldStyle: {
padding: theme.spacing(1, 1, 1, 0),
// vertical padding + font size from searchIcon
paddingLeft: `calc(1em + ${theme.spacing(4)}px)`,
transition: theme.transitions.create('width'),
width: '100%',
[theme.breakpoints.up('sm')]: {
width: '12ch',
'&:focus': {
width: '20ch',
},
},
}
}}
The searchFieldStyle parameter is ok inside the MaterialTable options.
Now due to the fact that you did not supply the whole code I cannot assume that you imported the theme correctly in your component. In the future please provide the amount of code for a minimum working variant, adding a working sandbox/fiddle will be better.
The theme can be used in a component using the useTheme hook from Material-UI read more HERE
import { useTheme } from "#material-ui/core/styles";
....
const theme = useTheme();
Now when you pass the style to the searchComponent of the MaterialTable you cannot use style and breakPoints as in JSS this is inline CSS.
So in order to use media queries you need to use the useMediaQuery hook, read more HERE
Also to make the styles based on breaking points it's better if we create a separate variable for the styles.
import useMediaQuery from "#material-ui/core/useMediaQuery";
import { useTheme } from "#material-ui/core/styles";
....
const theme = useTheme();
const smUp = useMediaQuery(theme.breakpoints.up("sm"));
let customStyle = {
padding: theme.spacing(1, 1, 1, 0),
// vertical padding + font size from searchIcon
paddingLeft: `calc(1em + ${theme.spacing(4)}px)`,
transition: theme.transitions.create("width"),
width: "100%"
};
if (smUp) {
customStyle = {
...customStyle,
width: "24ch",
color: "red",
}
};
}
...
options={{
...
searchFieldStyle: customStyle,
}}
As you can see the breaking point rule theme.breakpoints.up("sm") with the useMediaQuery and modify the customStyle variable.
The one thing that you cannot do is the pseudo :focus as React does not support inline css pseudo elements.
Also even if you achieve putting the focus there it will do you no good, due to the fact that the searchFieldStyle styles the parent div class="MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl MuiInputBase-adornedStart MuiInputBase-adornedEnd". If you focus on the actual input element will no trigger the focus on your Mui-Root container
I've also created a working sandbox that you can check HERE
The only viable solution if you want to use transition is to create your own input, override the <ToolbarComponent> as shown HERE in the example. After that create your own filter function for the data and pass it to the table

material-ui: AppBar: strategy for restricting an image height to AppBar height?

can anyone provide guidance around an idiomatic way to place an image in an AppBar and have it be restricted to the standard material height (e.g. 64px for a desktop)?
i'm currently using material-ui#next (1.0.0-beta.2 currently).
i have found that something like:
<AppBar>
<Toolbar>
<IconButton color="contrast" aria-label="Menu">
<MenuIcon />
</IconButton>
<img src={logo} style={{height: 64}}/>
<Typography type="title" color="inherit">
React Scratch
</Typography>
</Toolbar>
</AppBar>
works well.
the actual logo is a png file with a height greater than 64, so if i don't ratchet it down, it expands the height of the AppBar out of Material spec.
in the current master branch version of src/styles there is a getMuiTheme.js which seems to deliver this height readily, but in the #next version i am looking at, that file doesn't even exist and tbh, i can't easily determine how that height is being set anymore.
i found that the AppBar is currently being renovated for composability, so that churn might make it challenging to answer this question, but just in case anyone has a good handle on this, i figured i would toss the question out there.
thanks!
In all cases I've seen, an AppBar is implemented with a Toolbar as it's first child. The Toolbar's stylesheet dictates it's height based on the breakpoints defined in the theme.
Take a look here: https://github.com/callemall/material-ui/blob/v1-beta/src/Toolbar/Toolbar.js
You can use a similar approach to define a stylesheet with a class for your AppBar images that varies the height for the applicable breakpoints. Then when rendering the component, apply the class to your image.
Note: if you use the withStyles HOC, as is done in the Toolbar, AppBar etc, the classes defined in that stylesheet will be available through a prop named classes.
You are right about the AppBar's need for composability, but that issue has not been solved yet, and this is the beta branch anyway. When it is solved, there should be a better solution that would be worth migrating towards.
I hope this answer helps. I would have added code samples but I am answering from my phone while waiting in a grocery store parking lot. If I get a chance I will update this answer.
Here's one approach, duplicating the styles in a new reusable component:
import createStyleSheet from 'material-ui/styles/createStyleSheet';
import withStyles from 'material-ui/styles/withStyles';
// define these styles once, if changes are needed because of a change
// to the material-ui beta branch, the impact is minimal
const styleSheet = createStyleSheet('ToolbarImage', theme => ({
root: {
height: 56,
[`${theme.breakpoints.up('xs')} and (orientation: landscape)`]: {
height: 48,
},
[theme.breakpoints.up('sm')]: {
height: 64,
},
},
}));
// a reusable component for any image you'd need in a toolbar/appbar
const ToolbarImage = (props) => {
const { src, classes } = this.props;
return (
<img src={src} className={classes.root} />
);
};
// this higher order component uses styleSheet to add
// a classes prop that contains the name of the classes
export default withStyles(styleSheet)(ToolbarImage);
Another approach is to add the standard toolbar heights to the theme as business variables, override the root class for all Toolbars so that it makes use of them, and use the theme whenever you need to reference them again:
// define the standard heights in one place
const toolbarHeights = {
mobilePortrait: 56,
mobileLandscape: 48,
tabletDesktop: 64,
};
// create the theme as you normally would, but add the heights
let theme = createMuiTheme({
palette: createPalette({
primary: blue,
accent: pink,
}),
standards: {
toolbar: {
heights: toolbarHeights,
},
},
});
// recreate the theme, overriding the toolbar's root class
theme = createMuiTheme({
...theme,
overrides: {
MuiToolbar: {
// Name of the styleSheet
root: {
position: 'relative',
display: 'flex',
alignItems: 'center',
minHeight: theme.standards.toolbar.heights.mobilePortrait,
[`${theme.breakpoints.up('xs')} and (orientation: landscape)`]: {
minHeight: theme.standards.toolbar.heights.mobileLandscape,
},
[theme.breakpoints.up('sm')]: {
minHeight: theme.standards.toolbar.heights.tabletDesktop,
},
},
},
},
});
Then you can reference these heights in any stylesheet you create because they're part of the theme.
UPDATED FOLLOWING THE RELEASE OF 1.0.0-beta.11:
There is now a toolbar mixin available on the theme that provides the toolbar minHeight for each breakpoint. If you need to style an element relative to the standard height of the AppBar component, you can use this object to build your own styles:
const toolbarRelativeProperties = (property, modifier = value => value) => theme =>
Object.keys(theme.mixins.toolbar).reduce((style, key) => {
const value = theme.mixins.toolbar[key];
if (key === 'minHeight') {
return { ...style, [property]: modifier(value) };
}
if (value.minHeight !== undefined) {
return { ...style, [key]: { [property]: modifier(value.minHeight) } };
}
return style;
}, {});
In this example, toolbarRelativeProperties returns a function that will return an object that can be spread into your style object. It addresses the simple case of setting a specified property to a value that is based on the AppBar height.
A simple usage example would be the generation of a dynamic CSS expression for height calculation, which is depending on the standard height of the AppBar:
const componentStyle = theme => ({
root: {
height: toolbarRelativeProperties('height', value => `calc(100% - ${value}px)`)(theme)
}
});
The generated style definition might look like this:
{
height: 'calc(100% - 56px)',
'#media (min-width:0px) and (orientation: landscape)': {
height: 'calc(100% - 48px)'
},
'#media (min-width:600px)': {
height: 'calc(100% - 64px)'
}
}

Resources