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)} />
Related
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>
);
When I use react aria's mergeProps to merge button props and then pass to my component it causes it to override (I'm guessing) the initial props, but not all. The background color won't appear but everything else does, and the styling works fine on hover or any secondary "conditional" props.
Code:
export default function Button(props: ButtonProps): ReactElement {
const p = { ...DEFAULT_PROPS, ...props };
const ref = useRef<HTMLButtonElement>(null);
const { buttonProps, isPressed } = useButton(p, ref);
const { hoverProps, isHovered } = useHover({ isDisabled: p.isDisabled });
const behaviorProps = mergeProps(buttonProps, hoverProps);
return (
<button
className={clsx([
'button-main',
{
'is-hovered': isHovered,
'is-pressed': isPressed,
'is-secondary': p.variant === 'secondary',
},
])}
{...behaviorProps}
>
{p.children}
</button>
);
}
I am doing a project where I am trying to achieve the hover effects where the element enlarge and background color change. I made it happen toggling class but when I try to make the component I made more reusable by setting the props into the module I realize toggling class maybe not be a good option.So I tried to add inline style when hover.
I find the solution but the way to achieve it doesn't make a lot of sence to me, instead of adding lots of element.style.arribute="somthing"(if there is more than one attribute to change) I would like to just add one object with list of attributes inside.
So instead of
wrapper.current.style.backgroundColor = "red"; wrapper.current.style.color = "blue"; wrapper.current.style.fontSize = "2rem";
I believe when there is a lot of attributes to be manipulate it makes more sense if it can be like something like this:
wrapper.current.style = { backgroundColor: "red",color:blue,fontSize:2rem, };
But this just won't work for me.
snippet of this component here
import React from "react";
import { useRef } from "react";
import { useEffect } from "react";
import { useState } from "react";
// img:"../sources/computer-and-chair.jpg"
// imgName: "computer and chair"
// subtitle:"First website finnished with vanilla javascript"
function Project(props) {
const [isHover, setIsHover] = useState(false);
const wrapper = useRef(null);
useEffect(() => {
if (isHover) {
wrapper.current.classList.add("effect");
// wrapper.current.style.backgroundColor = "red"; work this way but not the way below...
wrapper.current.style = { backgroundColor: "red" };
} else {
wrapper.current.classList.remove("effect");
// wrapper.current.style.backgroundColor = "transparent"; work this way but not the way below...
wrapper.current.style = { backgroundColor: "red" };
}
}, [isHover]);
return (
<section ref={wrapper} className="single-project-container">
<div className="single-project-image-container">
<img
src={props.img}
alt={props.imgName}
className="project-image"
onMouseOver={() => {
setIsHover(true);
}}
onMouseOut={() => {
setIsHover(false);
}}
/>
</div>
<h1>{props.subtitle}</h1>
</section>
);
}
export default Project;
I have tried those:
Looked into inline-styling, no direcly answer to my question.
Looked into element.style from MDN, no direcly answer to my question.
Goal: Implement dark mode in react native application.
A little introduction to the system:
File structure:
Profile.ts
ProfileCss.ts
constants.ts
In my application in order to separate styles from components, I moved them into .ts files and exported as modules.
Example of StyleCss.ts:
import {StyleSheet} from 'react-native';
import {Window, FixedCSS, hsize, wsize} from '../../entities/constants';
export default StyleSheet.create({
loading: {
backgroundColor: FixedCSS.Colors.MainBackgroundColor,
}
...
});
All style-related constant values stored in constants.ts such as Colors, LineHeights, and etc. Also constants.ts contains a class called FixedCSS which has static properties:
export class FixedCSS {
static isDark = true;
static Colors = {
// Main Colors
RootColor: FixedCSS.isDark ? '#FF6666' : '#FF6666',
MainBackgroundColor: FixedCSS.isDark ? '#050505' : '#FFFFFF',
...
}
...
}
isDark property of FixedCSS is supposed to be changed with the help of Switch component in Profile.ts
<Switch
value={darkMode}
onValueChange={value => {
this.setState({darkMode: value});
}}
/>
The problem: How can I change isDark property in FixedCSS class so that it will affect the appearance of components. The most problematic aspect is that StyleCSS.ts files do not reflect to the change of isDark property in FixedCSS class, as they are exported only once when js bundle gets created.
Changing a static property of a class is not going to trigger re-renders the way that it's supposed to. This needs to make use of state somehow. I recommend a theme Context. You could use one context or have separate contexts for controlling isDarkMode and providing the CSS, which is what I am doing here.
In order to export a value only once but have that value stay current, the exported value can be a function rather than a constant. I am making the fixed CSS be a function of isDark and the individual component styles be a function of FixedCSS.
constants.ts
export const createFixedCSS = (isDark: boolean) => ({
isDark,
Colors: {
// Main Colors
RootColor: isDark ? "#FF6666" : "#FF6666",
MainBackgroundColor: isDark ? "#050505" : "#FFFFFF"
...
}
...
});
export type FixedCSS = ReturnType<typeof createFixedCSS>;
export const IS_DARK_DEFAULT = false;
cssContext.ts
// context to provide the FixedCSS object, which also includes isDark
const FixedCSSContext = createContext(createFixedCSS(IS_DARK_DEFAULT));
// context to provide a function for switching modes
// type Dispatch<SetStateAction<T>> allows for setting based on previous value
type SetModeContextValue = Dispatch<SetStateAction<boolean>>;
// needs an initial value that fits the type signature
const SetModeContext = createContext<SetModeContextValue>(() => {
throw new Error(
"cannot set `isDark` outside of a `DarkModeContext` Provider"
);
});
// combine both contexts into one Provider component
export const CSSProvider: FC<{}> = ({ children }) => {
// store the isDark state
const [isDark, setIsDark] = useState(IS_DARK_DEFAULT);
// update the FixedCSS when isDark changes
// I'm not sure if memoizing is actually necessary here?
const FixedCSS = useMemo(() => createFixedCSS(isDark), [isDark]);
return (
<SetModeContext.Provider value={setIsDark}>
<FixedCSSContext.Provider value={FixedCSS}>
{children}
</FixedCSSContext.Provider>
</SetModeContext.Provider>
);
};
// I prefer to export the hooks rather than the contexts themselves
export const useFixedCSS = () => useContext(FixedCSSContext);
export const useSetMode = () => useContext(SetModeContext);
// helper hook for `StyleCss.ts` files
// takes the makeStyles factory function and turns it into a style object by accessing the FixedCSS context
export const useStyleFactory = <T extends StyleSheet.NamedStyles<T>>(
factory: (css: FixedCSS) => T
): T => {
const FixedCSS = useFixedCSS();
return useMemo(() => factory(FixedCSS), [FixedCSS, factory]);
};
ProfileCss.ts
export const makeStyles = (FixedCSS: FixedCSS) => StyleSheet.create({
loading: {
backgroundColor: FixedCSS.Colors.MainBackgroundColor,
}
});
Profile.ts
const DarkModeToggle = () => {
// access isDark from global FixedCSS
const {isDark} = useFixedCSS();
// access update callback
const setIsDark = useSetMode();
return (
<Switch
value={isDark}
onValueChange={setIsDark}
/>
)
}
const Profile = () => {
// pass the imported `makeStyles` to the hook and get stateful styles object
const styles = useStyleFactory(makeStyles);
return (
<View>
<DarkModeToggle/>
<View style={styles.loading}/>
</View>
);
};
CodeSandbox Demo (all in one file)
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.