React styled-components theme-provider dynamic theme - javascript

I'm trying to achieve dark and light themes in my React app. I know how themes work, so I'm configuring my button like below, for example :
const Button = styled.button`
/* some styles */
color: ${props => props.theme.main}
`;
Then I'm defining themes as consts:
const dark = {
main: 'black',
text: 'switch to light mode'
};
const light = {
main: 'white',
text: 'switch to dark mode'
};
And when I want to use the theme somewhere I do it like this:
<ThemeProvider theme={dark}>
<Button>{dark.text}</Button>
</ThemeProvider>
But what I want to achieve is to change the theme dynamically (on a click function on the button probably). I'm kind of new to React so please don't be mean to me.

Something like this? Demo
import React, { Component } from 'react';
import { render } from 'react-dom';
import styled, { ThemeProvider } from 'styled-components';
const themes = {
'light': {
main: '#EFEFEF',
},
'dark': {
main: '#666',
}
}
const DynamicDiv = styled.div`
background: ${({ theme }) => theme.main};
`
class App extends Component {
constructor() {
super();
this.state = {
name: 'React',
theme: themes['light']
};
}
handleDark = () => {
this.setState({ theme: themes['dark'] })
}
render() {
return (
<ThemeProvider theme={this.state.theme}>
<div>
<DynamicDiv>{this.state.name}</DynamicDiv>
<div onClick={this.handleDark}>Change to Dark</div>
</div>
</ThemeProvider>
);
}
}
render(<App />, document.getElementById('root'));

Related

Chakra UI custom component variant not working

I have created a customTheme file to extend the Chakra them and have created a custom component called card which I would like to have variants applicable, but for some reason the base styles show and the variants never work.
extendTheme.js
`
import { extendTheme } from '#chakra-ui/react';
import { ButtonStyles as Button } from './styles/components/ButtonStyles';
import { CardStyle as Card } from './styles/components/CardStyle';
const customTheme = extendTheme({
colors: {
brand: {
100: '#f7fafc',
900: '#f77070',
},
grey: {
100: '#eff3fa',
},
blue: {
100: '#0098ae',
},
red: {
100: '#ff3d3d',
200: '#f77070'
},
},
fonts: {
body: 'Graphik Font',
heading: 'Graphik Font',
},
fontWeights: {
hairline: 100,
thin: 200,
light: 300,
normal: 400,
medium: 500,
semibold: 600,
bold: 700,
extrabold: 800,
black: 900,
},
components: {
Button,
Card,
}
});
export default customTheme;
`
cardStyle.js
`
import { defineStyleConfig } from '#chakra-ui/react'
export const CardStyle = defineStyleConfig({
// The styles all Cards have in common
baseStyle: {
display: 'flex',
flexDirection: 'column',
background: 'white',
alignItems: 'center',
gap: 6,
},
// Two variants: rounded and smooth
variants: {
rounded: {
padding: 8,
borderRadius: 'xl',
boxShadow: 'xl',
},
smooth: {
padding: 6,
borderRadius: 'base',
boxShadow: 'md',
},
},
// The default variant value
defaultProps: {
variant: 'smooth',
},
})
`
Card.jsx
`
import { Box, useStyleConfig } from '#chakra-ui/react'
function CardTest(props) {
const { variant, ...rest } = props
const styles = useStyleConfig('Card', { variant })
// Pass the computed styles into the `__css` prop
return <Box __css={styles} {...rest} />
}
export default CardTest
`
including the card in another jsx
`
<CardTest variant='rounded'>
<Image
src={imageOne}
rounded='full'
w={32}
h={32}
boxShadow='md'
/>
<Heading mt={6} maxW={60} size='lg' textAlign='center' color='gray.700'>
Explore the outdoors
</Heading>
<Text mt={6} mb={6} size='sm' color='brand.900'>
some text in the card
</Text>
<Image src={imageOne} w={32} h={32} />
</CardTest>
`
import React from 'react';
import ReactDOM from "react-dom/client";
import { ChakraProvider } from '#chakra-ui/react';
import customTheme from './extendTheme';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<ChakraProvider theme={customTheme}>
<App />
</ChakraProvider>
</React.StrictMode>,
);
it uses the example off of the chakra docs but for some reason the variant never changes and I cannot see why when if i tell it o use Button as styles and not Card the variants work for button.
Aha! You're mapping CardStyles to Card, but actually rendering CardTest!
import { CardStyle } from './styles/components/CardStyle';
const customTheme = extendTheme({
// ...
components: {
Button,
Card: CardStyle, // targets Card component...
CardTest: CardStyle, // ...but you're rendering CardTest
}
});
Here's a working version, slightly simplified:
https://codesandbox.io/s/so-74816092-oio6qr?file=/src/index.js
Side note, CardStyle can't apply to Card, as it's a multipart component:
https://chakra-ui.com/docs/styled-system/component-style#single-part-and-multipart-components
https://chakra-ui.com/docs/styled-system/advanced-theming
https://chakra-ui.com/docs/components/card/theming
Outdated
You haven't included a snippet showing where you provide your custom theme to Chakra's ThemeProvider, so this may explain your issue:
// extendTheme.js
import { extendTheme } from "#chakra-ui/react"
const theme = extendTheme({
// ...
})
export default theme
import * as React from 'react'
import { ChakraProvider } from '#chakra-ui/react'
import theme from './extendTheme.js'
function App() {
// Wrap ChakraProvider at the root of your app
return (
<ChakraProvider theme={theme}>
<App />
</ChakraProvider>
)
}
// Now you can use these colors in your components
function Usage() {
return <Box bg="brand.100">Welcome</Box>
}
https://chakra-ui.com/docs/styled-system/customize-theme

Changing theme primary color not affect button color

"#mui/material": "^5.5.3"
Hi there,
I'm trying to style my MUI form using values, imported from the SCSS variables.
Everything seems to be working fine (I can see passed values in devtools) except for the colors actually never change.
Registration.tsx
import {AppDataContext} from './context/app-data-context';
import {createTheme, ThemeProvider} from '#mui/material/styles';
export const Registration = () => {
const appData = useContext(AppDataContext);
const myTheme = useMemo(() => {
const colors = appData.brandedSCSS.default;
return createTheme({
palette: {
primary: {
main: colors.primary // #fdab0a
},
secondary: {
main: colors.secondary // #ffffff
}
}
})
}, [appData])
return (
<ThemeProvider theme={myTheme}>
// some code here
</ThemeProvider>
);
Somewhere going down the components tree:
import {Button, CircularProgress, Grid} from "#mui/material";
// some code here
return (
//some code there
<Button
color="primary"
type="submit"
>
)
Theme seems to be applied correctly
but the buttons is still blue
What am I doing wrong? Thanks.
this is how i'm changing the default theme colors of mui
index.js
import { ThemeProvider } from "#mui/material"
// or import { ThemeProvider } from '#emotion/react';
import { createTheme } from '#mui/material/styles';
const theme = createTheme({
palette: {
primary: {
main: '#554149',
contrastText: '#fff',
},
secondary: {
main: '#00AA9C',
contrastText: '#fff',
},
info: {
main: "#906D9A",
contrastText: '#fff',
},
},
})
<ThemeProvider theme={theme}>
<App/>
</ThemeProvider>
"#mui/material": "^5.0.6",

How to pass a prop to an imported function that returns a component in React?

I am trying to pass an argument back to my component library that is imported in a parent app as a dependency, but I am not entirely sure how to achieve this and I was hoping somebody could tell me what am I doing wrong. The idea is that on customer login we will determine which brand are they with and switch the theme that should be used going forward (I added a simplified example rendering a SpecialComponent)
Currently I have a Theme object in my component library that returns a Theme component wrapped in ThemeProvider. This was working fine until I tried to expand the concept of the theme object and add a concept of a brand.
Here is my Theme.js:
import React from 'react';
import PropTypes from 'prop-types';
import { ThemeProvider } from 'styled-components';
export const theme = config => {
const Theme = (props) => (
<ThemeProvider theme={config}>
{props.children}
</ThemeProvider>
);
Theme.propTypes = {
children: PropTypes.node.isRequired
};
return Theme;
};
const LightThemes = {
Adidas: {
name: 'Light',
variant: 'white',
background: 'red',
color: 'green',
textColor: 'white'
},
Nike: {
name: 'Light',
variant: 'red',
background: 'black',
color: 'yellow',
textColor: 'black'
}
};
const Light = ({ brand }) => theme(LightThemes[brand]);
const Theme = {
Light
};
export default Theme;
And here is how I am calling it within my app:
import React from 'react';
import {
SpecialComponent,
Theme
} from '#my-component-library';
const App = () => (
<Theme.Light brand={'Adidas'}>
<SpecialComponent />
</Theme.Light>
);
export default App;
As you can see I am trying to pass the string of Adidas to <Theme.Light> however this doesn't work and I get an error back saying Warning: Functions are not valid as a React child..
Before I added the concept of a brand in, my Theme.js looked like this and it was working fine:
import React from 'react';
import PropTypes from 'prop-types';
import { ThemeProvider } from 'styled-components';
export const theme = config => {
const Theme = (props) => (
<ThemeProvider theme={config}>
{props.children}
</ThemeProvider>
);
Theme.propTypes = {
children: PropTypes.node.isRequired
};
return Theme;
};
const LightThemes = {
name: 'Light',
variant: 'white',
background: 'red',
color: 'green',
textColor: 'white'
};
const Light = theme(LightThemes);
const Theme = {
Light
};
export default Theme;
I believe this is because previously I had a HOC and now that became a function that returns a component, so I can't use it in the same way that I used in the past. I am struggling to understand how to do this though.
Well, it's right there in the error. Theme.Light is a function, not a component, meaning that the way to make it work is with Theme.Light({ brand: 'BrandName' }).
This might help:
const lightCmp = Theme.Light({ brand: 'Adidas' });
const App = () => (
<lightCmp>
<SpecialComponent />
</lightCmp>
);
EDIT:
The reason it worked before is because to Light you are assigning the return of theme() which is a component, in your new version you need to instantiate the function first. Basically, you're doing the same thing with an extra step.
Another solution could be to have a hook useTheme({ brand }) that returns a HoC component.

Change color and position of CircularProgress?

I'm trying to use CircularProgress provided by Material.
I created this component in order to change its color:
import React, { Component } from 'react';
import { withStyles } from '#material-ui/core/styles';
import { CircularProgress } from '#material-ui/core';
class ColoredCircularProgress extends Component {
render() {
const { classes } = this.props;
return <CircularProgress {...this.props} classes={{colorPrimary: classes.colorPrimary}}/>;
}
}
const styles = props => ({
colorPrimary: {
backgroundColor: '#FD8907',
}
});
export default withStyles(styles)(ColoredCircularProgress);
However on my site it looks like this:
My questions are :
I want the circle to look orange and instead the circle looks still blue and it adds a square orange box behind.
It also displays at the top left corner of my site. How can I place it right in the center?
To change the color you can simple do this:
<CircularProgress style={{'color': 'yellow'}}/>
It works for Material-UI v4.x (I didn't try with minor versions)
You can override the style by applying css on .MuiCircularProgress-colorPrimary class.
Try this, hope this will work.
Example
.MuiCircularProgress-colorPrimary {
color: green !important;
}
.MuiCircularProgress-root {
left: 43%;
position: absolute;
top: 44vh;
}
Add this to the overrides in your theme. To make the color change globally.
MuiCircularProgress:{circle:{color:"green"},}
You don't need to override css.
Here's my solution:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import { CircularProgress } from '#material-ui/core';
const defaultSize = 50;
class ColoredCircularProgressComponent extends Component {
render() {
const { classes, size } = this.props;
return <CircularProgress {...this.props} classes={classes} size={size} />;
}
}
class ColoredCircularProgress extends Component {
render() {
const WithStylesComponent = withStyles(theme => ({
colorPrimary: {
color: this.props.foreColor
},
root: {
top: `calc(50% - ${this.props.size / 2}px)`,
left: `calc(50% - ${this.props.size / 2}px)`,
position: 'absolute'
}
}))(ColoredCircularProgressComponent);
return <WithStylesComponent {...this.props} />;
}
}
ColoredCircularProgress.propTypes = {
classes: PropTypes.object,
size: PropTypes.number,
foreColor: PropTypes.string
};
ColoredCircularProgress.defaultProps = {
size: defaultSize,
foreColor: 'green'
};
export default ColoredCircularProgress;

React HOC - Generic Container

Wizard and WizardPage
I am trying to build a generic Wizard and WizardPages to be generic a reusable across my app. I think the Wizard component as the one in charge of managing the state, and the WizardPage that is going to work as a wrapper, and will render Back, Cancel and Next buttons. It's suppose that the Next button (maybe the Back too) should dispatch an redux action. This action, could be different depending on the component being wrapped. This is what i have (not complete version):
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import withWizardLayout from '../../hoc/WithWizardLayout';
class Wizard extends Component {
constructor(props) {
super(props);
this.state = {
page: 0,
};
}
nextPage = () => {
this.setState({ page: this.state.page + 1 });
};
previousPage = () => {
this.setState({ page: this.state.page - 1 });
};
render() {
const { wizardPages } = this.props;
const { page } = this.state;
const wizardItem = wizardPages[page];
const nextPage = this.nextPage;
const previousPage = this.previousPage;
const ComponentWrapped = withWizardLayout(wizardItem.ComponentWrapped, nextPage, previousPage);
return (
<ComponentWrapped />
);
}
}
export default Wizard;
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { compose } from 'react-apollo';
import { connect } from 'react-redux';
import { Flex, Box } from 'reflexbox';
import { BlueRoundButton, GreyRoundButton } from '../components/Button';
export default function withWizardLayout(ComponentDependent, nextPageCallBack, previousPageCallback) {
class WizardPage extends Component {
previousPage = () => {
};
nextPage = () => {
this.props.test();
};
render() {
const { ComponentWrapped, isFirstPage, isLastPage } = ComponentDependent;
return (
<Flex column>
<ComponentWrapped
isLastPage={isLastPage}
isFirstPage={isFirstPage}
>
<Flex justify='flex-end'>
<Flex mr='auto' w={0.1}>
<GreyRoundButton style={{ fontWeight: '500', position: 'relative', top: '10%' }}>Back</GreyRoundButton>
</Flex>
<Flex w={0.08} mr={2} align='center' justify='center' style={{ textAlign: 'center' }}>
<span>Cancel</span>
</Flex>
<Flex w={0.15} justify='center'>
<BlueRoundButton onClick={this.nextPage} style={{ position: 'relative', top: '10%', fontWeight: '500' }}>Continue</BlueRoundButton>
</Flex>
</Flex>
</ComponentWrapped>
</Flex>
);
}
}
CustomersContainer
import { compose } from 'react-apollo';
import { connect } from 'react-redux';
import CustomerImport from '../components/Settings/CustomerImport';
import { withWizardLayout } from '../hoc/WithWizardLayout';
const mapStateToProps = null;
const mapDispatchToProps = dispatch => (
{
test: () => (dispatch(console.log("hi"))),
}
);
export default compose(connect(null, mapDispatchToProps))(CustomerImport);
Connected Component
import React, { Component } from 'react';
import styled from 'styled-components';
import {
SettingBlock,
NotifyBlock,
SuccessMessage,
ErrorMessage,
Instructions,
} from './styles';
import ExcelUploader from '../ExcelUploader';
const ProgressBar = styled.div`
width: 0;
height: 30px;
background-color: Green;
`;
const ExcelWrapper = styled.div`
margin-bottom: 4rem;
`;
export default class CustomerImport extends Component {
constructor(props) {
super(props);
this.state = {
progress: 0,
notify: {
success: {
message: '',
active: false,
},
error: {
message: '',
active: false,
},
},
};
}
render() {
const { success, error } = this.state.notify;
const ButtonsWrapper = this.props.children;
return (
<div>
<NotifyBlock>
<SuccessMessage className={success.active ? 'active' : null}>{success.message}</SuccessMessage>
<ErrorMessage className={error.active ? 'active' : null}>{error.message}</ErrorMessage>
</NotifyBlock>
<SettingBlock>
<h3>
Import your customers
</h3>
<ProgressBar style={{ width: `${this.state.progress}%` }} />
<Instructions>
Select an Excel file containing your customer information.
Need a template? Grab one here.
</Instructions>
<ExcelWrapper>
<ExcelUploader />
</ExcelWrapper>
{ButtonsWrapper}
</SettingBlock>
</div>
);
}
}
This is how i am supposed to render the Generic Wizard, we can have X wizardPages:
const wizardPages = [
{
ComponentWrapped: CustomersContainer,
isFirstPage: false,
isLastPage: false,
},
];
<Wizard wizardPages={wizardPages} />
The problem with this approach, is that i want on the onClick of the buttons in withWizardLayout:
1) Execute the callback on the Father (that's possible, i am passing as prop the handler)
2) Call the dispatch action received of the container. (i am not able not access the dispatched action, in this case, this.props.test)
How can i refactor this, to think a generic way to handle this? I think another ways to refactor this (having different withWizardLayout functions, but i am having render problems on the console).
Maybe i didn't architect this in the best way.
Help!

Categories

Resources