Chakra UI color mode in default props - javascript

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.

Related

CSS Not Loading with passed Property in withStyles of MUI

I have a functional react component with a style defined as below:
const StyledInnerItem = withStyles((theme) => ({
bgColor: {
backgroundColor: (props) => {
console.log('styledInnerItem color: ', props.color);
return 'red';
}
}
}))(InnerItem);
Basically I am trying to pass the props.color as the background color, however it's not even returning red which is set temporarily as the bg Color.
When I log the props, it is returning the color from the first render, and it is adding in the html file. but the styles only get added when I click on my component and the colors get applied to the component.
It works fine when the background Color is not a function and I need it to work the same by reading the color from the props.
The issue might be because the withStyles HOC only updates the styles when the component re-renders, but the props passed to the component do not trigger a re-render. One way to solve this is to pass the color value to the component as a state and update the state when the props change, which will trigger a re-render and the styles will get updated.
Here's how you can implement this:
const StyledInnerItem = withStyles((theme) => ({
bgColor: {
backgroundColor: (props) => props.color
}
}))(InnerItem);
class InnerItemWrapper extends React.Component {
state = {
color: 'red'
};
componentDidUpdate(prevProps) {
if (this.props.color !== prevProps.color) {
this.setState({ color: this.props.color });
}
}
render() {
return <StyledInnerItem color={this.state.color} />;
}
}
In this way, when the props.color changes, the component re-renders and the styles get updated accordingly.

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>
);

React Native (or React) listen to the style changes for the dark mode in separate ts modules

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)

CSS variables in Material ui - createMuiTheme

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);}

React Native set style as State

I want to use backgroundColor of style1 as a state, and change it in the function change().
How can I access style1?
My point is to call the function change from another function, making the button change its color to yellow, then change it's colour to blue again after sometime.
export default class App extends Component{
constructor (props){
super(props);
this.state = {
//style1.backgroundColour: blue //? Can't
}
this.change=this.change.bind(this)
}
change() {
this.setState({ style1.backgroundColour: yellow }) //?
}
render(){
return (
<View style={styles.style1} > </View>
);
}
}
const styles = StyleSheet.create({
style1:{
padding: 5,
height: 80,
width: 80,
borderRadius:160,
backgroundColor:'blue',
},
});
Update: This question and my answer was based on class components. But functional components and hooks are the current way of using React for a long time now.
First you should create a state for your backgroundColor:
const [backgroundColor, setBackgroundColor] = useState('blue');
then change it's value in your function
setBackgroundColor('yellow');
and finally, use it in style prop:
style={[styles.style1, {backgroundColor}}
Old answer, using class components
You can give an array to style prop. So you can put any other styles from another sources.
First you should declare a default value for your backgroundColor state:
this.state = {
backgroundColor: 'blue',
};
then set it's state in your function as normal string state:
this.setState({backgroundColor: 'yellow'});
and finally, use it in style prop:
style={[styles.style1, {backgroundColor: this.state.backgroundColor}]}

Categories

Resources