How to set breakpoints when overriding theme variables in createMuiTheme - reactjs

I'm trying to set the default theme typography font sizes to change based on breakpoints. For example, when the breakpoint is at the {xs} value, change theme.typography.h4.fontSize to '1.5rem'. At all other breakpoints, leave it at the default value ('2.125rem').
I can do this easily in components using the following code example:
const useStyles = makeStyles(theme => ({
title: {
[theme.breakpoints.down('xs')]: {
fontSize: '1.5rem',
}
},
}))
However, this means having to add the same code to every single component that has <Typography variant='h4'> in it. If I decide to change the value from '1.5rem' to '1.25rem', I would need to locate every single instance of <Typography variant='h4'> and change it manually.
Is there any way to add breakpoints to createMuiTheme so it applies to all instances?
I have attempted the following, which does not work:
const defaultTheme = createMuiTheme()
const theme = createMuiTheme({
typography: {
h4: {
[defaultTheme.breakpoints.down('xs')]: {
fontSize: '1.5rem'
}
}
}
})

You can use custom breakpoints with createBreakpoints.
Here is an example changing the MuiButton
import createBreakpoints from '#material-ui/core/styles/createBreakpoints'
const customBreakpointValues = {
values: {
xs: 0,
sm: 576,
md: 768,
lg: 992,
xl: 1200,
},
}
const breakpoints = createBreakpoints({ ...customBreakpointValues })
const theme = createMuiTheme({
breakpoints: {
...customBreakpointValues,
},
components: {
MuiButton: {
styleOverrides: {
root: {
padding: '10px',
[breakpoints.up('lg')]: {
padding: '20px',
},
},
},
},
},
})

CORRECTION: Initially I indicated that your solution didn't work because you were using an impossible condition, but I was incorrect. "down" from xs is inclusive of xs, so it does what you would want (matches 0px to 600px).
I'm not sure what I did in my initial testing that led me astray, but one thing that can cause confusion is if you have multiple down thresholds. In my own testing, I had an easier time avoiding shooting myself in the foot (e.g. by having a breakpoints.down("xs") followed by a later breakpoints.down("sm") that trumped the "xs" settings) by using breakpoints.only.
From https://material-ui.com/layout/breakpoints/#breakpoints
xs, extra-small: 0px or larger
sm, small: 600px or larger
Here is an updated sandbox that shows a little more clearly what is happening:

Related

fluent ui styling, border is not defined

i am using fluent ui which uses Griffel for styling. When i try to provide border: "none" it's throwing error that string not assignable to undefined
import {makeStyles} from "#fluentui/react-components";
export const settingsButtonStyle = makeStyles({
root: {
border: "none",
background: "#E5E5E5",
marginRight: "13px"
},
rootHovered: {
background: "#E5E5E5",
}
});
GriffelJS tries to make Atomic CSS classes, so they don't support CSS shorthands (like border or background) right away. But there is a helper function called shorthands to manage some of theses. Background is not included in the helper function, so there you should use the specific CSS proprety. So your code should look like this:
const useStyles = makeStyles({
root: {
...shorthands.border("0"),
// or
...shorthands.borderStyle("none"),
backgroundColor: "#E5E5E5",
marginRight: "13px"
},
})
You find more infos to shorthands in GriffelJS in their documentation shorthands - GriffelJS

Using theme palette colors in custom MUI component variants with light/dark mode

For readability reasons, I want to split the components object passed in the createTheme function (components may have large variants) and I do have light/dark mode.
According to docs, this is how we get the design tokens:
const getDesignTokens = (mode: PaletteMode) => ({
palette: {
mode,
...(mode === 'light'
? {
// palette values for light mode
primary: amber,
divider: amber[200],
text: {
primary: grey[900],
secondary: grey[800],
},
}
: {
// palette values for dark mode
primary: deepOrange,
divider: deepOrange[700],
background: {
default: deepOrange[900],
paper: deepOrange[900],
},
text: {
primary: '#fff',
secondary: grey[500],
},
}),
},
});
After that, I create the theme depending on whether the mode is light or dark.
The problem is when I'm trying to add a component to the theme (as stated before, each component will be only referenced in the createTheme, the definition would be somewhere else) I cannot use colors from the theme without wrapping the component is a function which has the parameter the mode used.
I wonder if there is any solution like with the sx prop when you're referencing the color as a string, let's say sx={{ backgroundColor: 'button.background' }} and that would automatically be used from the theme.
Wrapping each component in a function with a parameter does the job, but I would like to know if there is any better solution.
How the code is now:
const dashedVariants = (palette) => ({
props: {variant: 'dashed'},
style: {
border: `1px dashed ${palette.dashColor}`
}
})
const Button = (palette) => ({
styleOverrides: {},
variants: [dashedVariants(palette)]
})
vs what I'm trying to acheive:
const dashedVariants = {
props: {variant: 'dashed'},
style: {
border: `1px dashed palette.dashColor` //something like that??
}
}
Note: I've looked over this existing question, but unfortunately this does seem to help.
Due to complexity of CSS properties, many UI toolkit and its themes only parse singular properties(or provide a detour utility); MUI's one of them. Use separate border properties to make parser working. Palette color property parser only works for the type of React.CSSProperties['color'] property. .border property is Property.Border type. The color parser won't work in this case.
Palette type only works with appropriate properties. it does not provide dashColor property. according to MUI doc, working properties are:
.palette.primary.light
.palette.primary.main
.palette.primary.dark
.palette.primary.contrastText
// ...
.palette.secondary
.palette.error
.palette.warning
.palette.info
.palette.success
const theme = {
palette: {
secondary: {
main: '#...' // user defiend color
}
}
}
const dashedVariants = {
props: {variant: 'dashed'},
style: {
borderColor: 'secondary.main',
borderWidth: '1px',
borderStyle: 'dashed',
}
}
There's an experimental CSS variable feature. With this, it is possible to define CSS variable inside complex property. This is probably the closest to the goal but it's currently experimental stage, might be unstable for production use. I am also looking forward to using this in the future.

Target CSS child selector created by in material ui

I have styles like this:
const useStyles = makeStyles(theme => ({
root: {
margin: 5
},
container: {
backgroundColor: 'red'
},
active: {
// here I want to target the 'container' className I created above like
'& .container': {
backgroundColor: 'green'
}
}
});
I want to target the container className I created inside of the active className. The above won't work because in the DOM, MUI will generate a unique name so I won't be targeting the right class. Wasn't able to find any SO answer or blog or documentation addressing this.
$ rulename is used for this purpose. Here is the documentation of it on Material-UI.
CSS in JS documentation also explains this feature.
container: {
//other styles
},
active: {
"& $container": {
background: "green",
color: "#fff"
}
}
Here one thing which is important that for referencing 'containerrule, it should be defined in the rules object. trying to use"& $containerwithout defining thecontainerrule inside themakeStyles` will not work.
Here is the working demo:
You can refer using $
You will have to modify your DOM little bit such that the active className is not the parent of container. Instead add the active className to the conatiner element itself.
so your css style might look like below
const useStyles = makeStyles(theme => ({
root: {
margin: 5
},
container: {
backgroundColor: 'red',
'&$active': {
backgroundColor: 'green'
}
},
});
I think this is what you are looking for $rulename
How to Use $ruleName to reference a local rule within the same style sheet
In your case i think the solution would be
const useStyles = makeStyles(theme => ({
root: {
margin: 5
},
container: {
backgroundColor: 'red'
},
active: {
.container: {
backgroundColor: 'green'
}
}
});
Which should compile to
.root.container.active{}
and on the target tag taking a example of button here
<Button
classes={{
root: classes.root,
container: classes.container,
active: classes.active,
}}>
Havent worked with MUI yet but even in vue or react the way this is achived is by setting a dynamic name on the tag that is targeted via script.

MaterialUi custom breakpoints not applied

Based on this question, I tried to have a theme that uses a common config file(that should be used by other themes as well). So I figured the breakpoints would be a good starting point since they should be the same across all the themes.
I have the common part like
const BREAKPOINTS = {
xs: 0,
sm: 768,
md: 960,
lg: 1280,
xl: 1920,
};
const commonConstants = {
breakpoints: {
values: BREAKPOINTS,
},
};
export default commonConstants;
Then I create my theme like
const defaultTheme = responsiveFontSizes(createMuiTheme(myTheme, commonConstants));
If I console.log my theme object, it will display the correct breakpoint values but will not apply them(the default one will be applied). However, if the breakpoint object is added directly inside the theme objectmyTheme (i.e. not uside of the theme), the correct breakpoints are applied. What am I missing here? If the final theme object has the same structure, why is it working differently?
Several portions of the theme (e.g. palette, breakpoints, spacing, typography) have additional processing associated with them that creates additional entries in the theme based on the options passed in. This additional processing is only applied to the object passed as the first argument to createMuiTheme. Any additional arguments are merged in after the additional processing.
For breakpoints, that additional processing is contained in the createBreakpoints function. This creates the various functions that leverage the breakpoint values (e.g. theme.breakpoints.up, theme.breakpoints.down, etc.). When you pass in custom breakpoint values via a second argument, those values are not being used in the creation of those breakpoint functions.
There are two main options for addressing this.
Option 1: Apply the additional processing yourself
import { createMuiTheme } from "#material-ui/core/styles";
import createBreakpoints from "#material-ui/core/styles/createBreakpoints";
const BREAKPOINTS = {
xs: 0,
sm: 768,
md: 960,
lg: 1280,
xl: 1920
};
const breakpointsFull = {
breakpoints: createBreakpoints({
values: BREAKPOINTS
})
};
const myTheme = { other: "stuff" };
const theme = createMuiTheme(myTheme, breakpointsFull);
Option 2: Merge your custom breakpoint values into the first argument to createMuiTheme
import { createMuiTheme } from "#material-ui/core/styles";
const BREAKPOINTS = {
xs: 0,
sm: 768,
md: 960,
lg: 1280,
xl: 1920
};
const breakpointsValues = {
breakpoints: {
values: BREAKPOINTS
}
};
const myTheme = { other: "stuff" };
const theme = createMuiTheme({ ...myTheme, ...breakpointsValues });
Here's a working example demonstrating the problem with your current approach as well as the two alternatives:
import React from "react";
import { createMuiTheme } from "#material-ui/core/styles";
import createBreakpoints from "#material-ui/core/styles/createBreakpoints";
const BREAKPOINTS = {
xs: 0,
sm: 768,
md: 960,
lg: 1280,
xl: 1920
};
const breakpointsValues = {
breakpoints: {
values: BREAKPOINTS
}
};
const breakpointsFull = {
breakpoints: createBreakpoints({
values: BREAKPOINTS
})
};
const myTheme = { other: "stuff" };
const badTheme = createMuiTheme(myTheme, breakpointsValues);
const goodTheme1 = createMuiTheme(myTheme, breakpointsFull);
const goodTheme2 = createMuiTheme({ ...myTheme, ...breakpointsValues });
export default function App() {
return (
<ul>
<li>badTheme.breakpoints.values.sm: {badTheme.breakpoints.values.sm}</li>
<li>badTheme.breakpoints.up("sm"): {badTheme.breakpoints.up("sm")}</li>
<li>
goodTheme1.breakpoints.values.sm: {goodTheme1.breakpoints.values.sm}
</li>
<li>
goodTheme1.breakpoints.up("sm"): {goodTheme1.breakpoints.up("sm")}
</li>
<li>
goodTheme2.breakpoints.values.sm: {goodTheme2.breakpoints.values.sm}
</li>
<li>
goodTheme2.breakpoints.up("sm"): {goodTheme2.breakpoints.up("sm")}
</li>
</ul>
);
}
In order to effectively override breakpoints in the default MuiTheme (as shown in Ryan Cogswell answer Option 1) you must use the createBreakpoints function passing in the values and keys elements.
Based on Ryan's answer, I found the following solution to work.
import { createMuiTheme } from "#material-ui/core/styles";
import createBreakpoints from "#material-ui/core/styles/createBreakpoints";
const BREAKPOINTS = {
xs: 0,
sm: 768,
md: 960,
lg: 1280,
xl: 1920
};
const breakpointsFull = createBreakpoints({
values: {...BREAKPOINTS},
keys: Object.keys(BREAKPOINTS),
});
const myTheme = { other: "stuff" };
const theme = createMuiTheme({
default: myTheme,
breakpoints: breakpointsFull,
});
This strategy enables easy modification of the BREAKPOINTS variable, and correctly assigns it into the object. Using the createBreakpoints Mui function will generate functions for up and down, which allow you to check against breakpoints in your code for example:
const isSmallScreen = UseMediaQuery(theme.breakpoints.down('sm'))

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