CSS variables in Material ui - createMuiTheme - javascript

I am trying to create a material ui theme using the existing colors defined as css variables my-pallette.scss:
:root {
--primary-color: '#FF0000';
...
}
And use this like this:
import { createMuiTheme } from '#material-ui/core/styles';
export const muiTheme = createMuiTheme({
palette: {
primary: {
main: 'var(--primary-color)',
},
},
});
This throws an error:
Uncaught Error: Material-UI: Unsupported var(--primary-color) color.
We support the following formats: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla().
According to this Github thread: Support CSS variables as theme option this is unsupported at the moment.
Is there any workaround so I can use var(--primary-color) as a primary color in material ui createMuiTheme?
The end goal is to use material ui components in their simplest form with their primary, scondary, etc colors overwritten by my colors.
<Radio color="primary" />
I have tried using the colors from my pallets like this:
const cssVariables = {
primaryColor: getComputedStyle(document.documentElement).getPropertyValue('var(--primary-color)'),
};
and use cssVariables.primaryColor but this doesn't work and feels very counterintuitive.
My last solution is to duplicate the palette as a normal object as use it as it is but this seems a nightmare for maintenance.

The MUI team is currently working on a solution to support CSS variables. You can track the progress here and check the details here. There is also an official documentation here.
UPD of 21.12.2022
CSS variables are supported now by the MUI as an experimental API, check documentation here.

The workaround posted by NearHuscarl gets the job done, but there's a flicker caused by the palette change. This can be avoided with lazy initialization in useState.
const cssVar = (name: string) =>
getComputedStyle(document.documentElement).getPropertyValue(name).trim();
const App: React.FC = () => {
const [theme] = useState(() =>
createTheme({
palette: {
primary: {
main: cssVar("--color-primary"),
light: cssVar("--color-primary-light"),
dark: cssVar("--color-primary-dark"),
bg: cssVar("--color-primary-bg"),
contrastText: cssVar("--color-primary-contrast-text"),
},
},
})
);
return (
<ThemeProvider theme={theme}>
<YourApp />
</ThemeProvider>
);
};

The ugly workaround works, but I suspect you are calling getComputedStyle() in the module scope when the stylesheets may not be initialized yet.
Try putting it in the render method like this.
const [theme, setTheme] = React.useState(null);
React.useLayoutEffect(() => {
const color = getComputedStyle(document.documentElement)
.getPropertyValue("--your-css-color")
.trim(); // the result have a leading whitespace.
setTheme(
createMuiTheme({
palette: {
primary: {
main: color
}
}
})
);
}, []);
Live Demo

Setting colors from theme options may be more effectively. With this method you could define material ui colors as css variables.
import React, { useEffect } from 'react'
import { createTheme, ThemeProvider } from '#mui/material/styles'
import { indigo, amber } from '#mui/material/colors'
const themeOptions = createTheme({
palette: {
primary: { main: indigo[500] },
secondary: { main: amber[600] },
background: { default: '#F3F6F9' },
},
})
const colorKeys = ['background', 'common', 'error', 'grey', 'info', 'primary', 'secondary', 'success', 'text', 'warning']
const r = document.querySelector(':root')
const AppThemeProvider = ({ children }) => {
useEffect(() => {
colorKeys.forEach((color) => {
const themeColorObj = themeOptions.palette[color]
for (const key in themeColorObj) {
if (Object.hasOwnProperty.call(themeColorObj, key)) {
const colorVal = themeColorObj[key]
r.style.setProperty(`--color-${color}-${key}`, colorVal)
}
}
})
}, [])
return <ThemeProvider theme={themeOptions}>{children}</ThemeProvider>
}
export default AppThemeProvider
With this way you could use css variables as sass or common css.
.dx-link-edit {
color: var(--color-primary-main);}

Related

Reference chakra-ui custom colors in React components & theme

I'm using Chakra-UI for the first time, and extending the base theme. Following their Scaling Out Your Project suggestion, I've broken things out into separate files and then include them, so I have:
//theme/index.js
import { colors } from 'theme/foundations/colors';
const overrides = {
colors
};
export default extendTheme(overrides);
Then I have our broad color palette:
//theme/foundation/colors.js
export const colors = {
myBlue: {
50: '#e6f0ff',
...
800: '#0b1e41', // My Blue
900: '#010a1a'
},
myYellow: {
50: '#fff4dc',
...
400: '#f4a224', // My Yellow
...
}
}
Now what I want to do is name the specific hues:
//theme/foundation/colors.js
...
export const brand = {
myBrand: {
blue: colors.myBlue['800'],
yellow: colors.myYellow['400'],
clickable: colors.myYellow['400']
}
So I can do something like
//theme/global.js
a: {
color: 'myBrand.clickable'
}
Doing it exactly like above doesn't work (it shows up in the rendered code in dev inspector as color: myBrand.clickable with an "Unsupported property value" warning.
If I instead reference it as a variable, like so:
//theme/global.js
import { brand } from './foundations/colors';
a: {
color: brand.myBrand.clickable
}
it works as expected for styles applies from the theme (i.e., in the above case, all <a> elements are set to yellow), but I still can't access it as a chakra-named color in my React components. That is to say,
import { IconButton, useColorModeValue } from '#chakra-ui/react';
import { BiMailSend } from 'react-icons/bi';
const MyButton = () => {
return (
<IconButton bg={useColorModeValue('myBrand.clickable', 'myBrand.clickable')}
onClick={() => console.log('click')}
icon={<BiMailSend />
)
}
will render the icon, but not set the color. However, if I change 'myBrand.clickable' to a named Chakra color (e.g., either 'green', or 'green.400'), it works fine.
How do I set this up so I can use my own named colors both in the theme definition and in Chakra components throughout my code?
For your IconButton to also color the icon you need to provide the attribute colorScheme not just the bg
Updated code:
import { IconButton, useColorModeValue } from '#chakra-ui/react';
import { BiMailSend } from 'react-icons/bi';
const MyButton = () => {
return (
<IconButton colorScheme={useColorModeValue('myBrand.clickable', 'myBrand.clickable')}
onClick={() => console.log('click')}
icon={<BiMailSend />
)
}

How to use string to find object key?

How can I select an object key in a theme object using a string used in react component prop?
theme
{
palette: {
primary: {
main: '#dc2626',
dark: '#7f1d1d',
light: '#fecaca',
}
text: '#111827',
}
}
component
const Typography = ({ color, children }) => (
<p style={{ color: theme.palette[color] }}>{children}</p>
);
How I want to use the component:
<Typography color='primary.main'>Hello world</Typography>
Let me know if this is a bad practice and should be avoided anyway.
I tried to use the eval function but found out it was not secure and considered to be a bad practice.
If you are just looking how to get a nested field in an object, you should look at lodash's get function.
Example :
export default const theme = {
palette: {
primary: {
main: '#dc2626',
dark: '#7f1d1d',
light: '#fecaca',
}
text: '#111827',
}
};
Component
import { get } from lodash library;
import theme from './theme.js';
export default function MyComponent({ children }) {
const nestedFieldPath = 'palette.primary.main';
// Get nested field value
const nestedFieldValue = get(theme, nestedFieldPath);
return <p color={nestedFieldValue}>{children}</p>
}
You could make a hook that can be extended as well as handle a default incase no color is being passed to your typography component
Example hook:
import React from "react";
const useColor = (color = null) => {
// Set the default theme
const [selectedTheme, setSelectedTheme] = React.useState(["primary", "main"]);
React.useEffect(() => {
if (color) {
setSelectedTheme(color.split("."));
}
}, [color]);
return selectedTheme;
};
export default useColor;
Then the Typography component you're returning an array from the hook so deconstruct it into 2 values. Naming is of course questionable here :
const Typography = ({ color, children }) => {
const [level, choice] = useColor(color);
return <p style={{ color: theme.palette[level][choice] }}>{children}</p>;
};
Usage:
<Typography color={"primary.main"}>Hello</Typography>
A codesandbox : https://codesandbox.io/s/clever-julien-rr0tuf?file=/src/App.js:506-561
This would be my solution, without using any additional libraries.
Idea is to create a function with method for getting the nested color value from your Theme object and then use that function (getColor) in your components.
This can handle additional levels of nesting.
Bare in mind, my experience with React is reading 3 pages of docs.
It is not clear from question how you want to handle situation when you try to get color that is not in theme, you would have to handle that inside reduce.
function Theme () {
let palette = {
primary: {
main: '#dc2626',
dark: '#7f1d1d',
light: '#fecaca',
},
secondary : {
green: {
lightgreen : 'myfancycolor :)'
}
}
}
this.getColor = function(string){
let nesting = string.split(".");
const result = nesting.reduce(
(accumulator, currentValue) => {
if (accumulator[currentValue])
return accumulator[currentValue];
else
return accumulator
} ,
palette
);
return result;
}
}
let myTheme = new Theme();
console.log(myTheme.getColor("secondary.green.lightgreen"))
//prints myfancycolor :)
Usage in component:
const Typography = ({ color, children }) => (
<p style={{ color: myTheme.getColor("primary.dark") }}>{children}</p>
);

How to override CSS classes made with makeStyles

Note, I'm using MUI 4.12.3. In file A I have this (simplified) besides a functional component. Inside the functional component's return statement I also apply it to a JSX element (not shown). This works well.
const useStyles = makeStyles((t) => ({
content: {
minHeight: '100vh',
},
}));
In file B, I would like to override the CSS class content based on isDesktop. Is this possible/desirable? Something like this, but it does not work:
const useStyles = makeStyles({
content: {
minHeight: (props) => (props.isDesktop ? '100vh' : '112vh'),
},
});
//And inside my functional component:
const isDesktop = useMediaQuery(Theme.breakpoints.up('sm'));
const classes = useStyles({ isDesktop });
Note that the intent is to not render the JSX component in file B, only to override the CSS class content in file B is desirable. (classes is unused in my sample.)
This can be done with few hooks.Let say my functional componet name is "Mycomponent".my material component is "materialcomponent".we need to import useWindowSize hook.That helps us to get the window size.so that we can check our screen size is desktop or mobile.in the makestyle we have to create two classes for desktop and mobile minHeight.with the simple if else checking we can pass this two classes conditionally to materialcomponent className prop.Below is the code.
1.create two classes with the makestyles
const useStyles = makeStyles((t) => ({
contentDesktop: {
minHeight: '100vh',
},
contentMobile:{
minHeight: '110vh',
}
}));
2.import the useWindowSize hook
import useWindowSize from "hooks/useWindowSize";
3.functinal componet code.
const Mycomponent = () => {
const classes = useStyles();
let myclass="";
const width = useWindowSize();
const isDesktop = width >=1024;
const mobile= width <= 600;
if(isDesktop){
myclass=classes. contentDesktop;
}
if(mobile){
myclass=classes.contentMobile;
}
return (
<materialcomponent className={`${myclass}`}
);
}
You can export this function outside of your functional component
export const getStyles = (isDesktop) => {
return {
content: {
minHeight: isDesktop ? "100vh" : "112vh",
},
};
};
And then wherever you want your styling applied
const isDesktop = useMediaQuery(Theme.breakpoints.up('sm'));
...
<SomeMuiComponent sx={getStyles(isDekstop)} />

Chakra UI color mode in default props

I'm trying to extend my theme (modify default component) and pass my new default props to the Input component. I need focusBorderColor to change by current color mode. I tried to pass a function to defaultProps parameter but this broke all styles on my Input component:
const theme = extendTheme({
components: {
Input: {
defaultProps: props => ({
variant: 'filled',
focusBorderColor: props.colorMode === 'light' ? 'gray.200' : 'gray.600',
}),
},
},
})
I also tried to style my component by baseStyle property and this works, but all default base styles are overwritten by passing a function to it. I need to add property to baseStyle and preserve default baseStyles (only extend but with color mode).
const theme = extendTheme({
components: {
Input: {
baseStyle: props => ({
field: {
width: props.colorMode === 'dark' ? '50%' : '80%',
},
}),
},
},
})
Is there any way how to pass colorMode to default props? Or is there any way how to properly extend baseStyle with function declaration without losing default baseStyles?
defaultProps can only have the following keys: variant, size, and colorScheme.
To change the borderColor when the Input is focused you can do
const theme = extendTheme({
components: {
Input: {
variants: {
filled: (props) => ({ // Notice "props" here
field: {
_focusVisible: {
bg: "transparent",
// mode is an alternative to props.colorMode === x ? y
borderColor: mode("blue", "purple")(props)
}
}
})
}
}
}
});
Full code: https://codesandbox.io/s/chakra-ui-focus-bordercolor-muw9cv?file=/src/index.tsx
Chakra-UI's default theme variants will override your baseStyle. You can see their default styles here or you can use useMultiStyleConfig to see the styles of your Input component. So if the filled variant and other variants you want to use have a value for borderColor set in focus mode (set by Chakra-UI's default theme), you'll have to override this property inside the variant like in the example above.

How can I export a react material-ui component with 2 styles object? (material-ui V1)

We are using material-ui v1 and to compose HOC we are using recompose utility. The problem is we have 2 styles object. One is defined inside the component and other is general style object (defined in another jsx style file which exports javascript style objects that is being used system-wide). Now we need to compose both styles object. Is there any way to do that? composing 2 style objects with withStyles does not work. Any help will be greatly appreciated.
Here is our code:
import { connect } from 'react-redux';
import { withStyles } from 'material-ui/styles';
import compose from 'recompose/compose';
import { generalStyle } from '../../modules/styles/styles';//external style object
//internal style object
const styleObj = theme => ({
paper: {
padding: 16,
color: theme.palette.text.secondary,
},
});
class myCompo extends React.Component {
//..compo code
}
//composing 2 style objects with withStyles does not work
export default compose(
withStyles({ styleObj, generalStyle }),
connect(mapStateToProps, mapDispatchToProps),
)(myCompo );
//here is how generalStyle is defined in styles.jsx file
const generalStyle = theme => ({
floatR: {
float: 'right',
},
floatL: {
float: 'left',
},
parallelItmes: {
display: 'flex',
alignItems: 'center',
padding: theme.spacing.unit,
paddingLeft: theme.spacing.unit + 10,
},
});
module.exports = {
generalStyle,
};
Suppose you have generalyStyles defined something like this:
const generalStyles = theme => ({
generalStylesButton: {
backgroundColor: "#ff0000",
margin: theme.spacing.unit,
},
});
Then, you can merge generalStyles into your internalStyles by executing the function (I think this is what you're missing based on your code snippet in the comments) and then spreading the resulting object:
const internalStyles = theme => ({
internalStylesButton: {
backgroundColor: "#00ff00",
margin: theme.spacing.unit,
},
// Notice that since generalStyles is a function, not an object, you need
// to execute it before you can merge
...generalStyles(theme)
});
Then the styling from both sets of styles will be available:
function FlatButtons(props) {
const { classes } = props;
return (
<div>
<Button className={classes.internalStylesButton}>
Styled from internal
</Button>
<Button className={classes.generalStylesButton}>
Styled from general
</Button>
</div>
);
}
FlatButtons.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(internalStyles)(FlatButtons);
You can probably encapsulate this logic in an HOC to avoid duplicating the spread operator in each of your components. You probably also need to be careful about cases where generalStyles and internalStyles both have the same class names:
const general = theme => ({
duplicateName: {
backgroundColor: "#ff0000",
},
});
const internal = theme => ({
duplicateName: {
backgroundColor: "#00ff00",
},
...general(theme),
});
Having the spread operator reconcile duplicateName between these two styles might lead to tricky situations.

Categories

Resources