Is there a way to theme with emotion without "styled" - javascript

With emotion (https://github.com/emotion-js/emotion) I know I can theme with css and styled together with something like:
const carouselCss = ({ theme }) => css`
.. css properties etc
`;
const CarouselDiv = styled('div')`
${carouselCss};
`;
Then in JSX:
<ThemeProvider theme={{ stuff: 'blah' }}>
<CarouselDiv>
..
</CarouselDiv>
</ThemeProvider>
But is there any elegant way to theme with only css - prefer not to use componentized styles because I want to keep to semantic html elements in JSX (also have a massive code base and its easier to migrate from sass to emotion without having to use styled)
I know I can do something like:
const carouselCss = (theme) => css`
.. css properties etc
`;
then jsx:
<div className={carouselCss(this.props.theme)}>
..
</div>
But it means passing a theme prop all the time from the component - which is a little cumbersome
Is there a better way to do this ? Somehow wrap css with something so it has theme vars injected ?

ThemeProvider will get you that. Here is an example with both styled and css options shown :
/** #jsx jsx */
import { jsx } from '#emotion/core'
import styled from '#emotion/styled'
import { ThemeProvider } from 'emotion-theming'
const theme = {
colors: {
primary: 'hotpink'
}
}
const SomeText = styled.div`
color: ${props => props.theme.colors.primary};
`
render(
<ThemeProvider theme={theme}>
<SomeText>some text</SomeText>
<div css={theme => ({ color: theme.colors.primary })}>
some other text
</div>
</ThemeProvider>
)

You could use theme HoC. But if your concern is prop passing to slider, this HoC is basically doing the same, i.e. injects theme into this.props.theme.
Personally, i would use ThemeProvider API if possible, maybe with function-style theme, as pointed out in the docs:
// function-style theme; note that if multiple <ThemeProvider> are used,
// the parent theme will be passed as a function argument
const adjustedTheme = ancestorTheme => ({ ...ancestorTheme, color: 'blue' });

Related

Material ui use palette primary color based on selected theme mode

While using mui react, I want to color some non material ui custom components made with div using colors from my theme's primary palette.
I currently can use theme.palette.primary.main or the .light colors directly, including the theme.palette.text.primary for texts colors.
But if I change the theme to dark mode, I will have to change the color reference as well, by checking on theme.mode with conditionals like the following:
<div
style={{
backgroundColor:
theme.palette.mode == "light"
? theme.palette.primary.dark
: theme.palette.primary.light
}}
>
Test
</div>
So is there a way we can make this work just like in the material ui components? Passing theme.palette.primary will work with theme mode changes?
Maybe something simpler like:
<div style={{ backgroundColor: theme.palette.primary }}></div>
You could use Context to save your theme setting globally and also you need to separate the themes like light and dark.
// SettingsContext.js
import React, {
createContext,
useState
} from 'react';
const defaultSettings = {
// you could add sort of global settings here
theme: 'light'
};
const SettingsContext = createContext({
settings: defaultSettings,
saveSettings: () => {}
});
export const SettingsProvider = ({ settings, children }) => {
const [currentSettings, setCurrentSettings] = useState(settings || defaultSettings);
return (
<SettingsContext.Provider
value={{
settings: currentSettings,
}}
>
{children}
</SettingsContext.Provider>
);
};
export const SettingsConsumer = SettingsContext.Consumer;
export default SettingsContext;
You can build the settings context as a hook but you can skip this.
// useSettings.js
import { useContext } from 'react';
import SettingsContext from './SettingsContext';
const useSettings = () => useContext(SettingsContext);
export default useSettings;
And then try to create your custom theme which includes the dark and light mode.
// theme.js
import { createTheme as createMuiTheme } from '#mui/material/styles';
const themes = {
'light': {
...
},
'dark': {
...
}
];
export const createTheme = (name) => {
return createMuiTheme(themes[name]);
}
After that, you need to pass the theme which you have selected in App.js or index.js whatever top level file.
// App.js
...
import { ThemeProvider } from '#mui/material/styles';
import useSettings from './useSettings';
import { createTheme } from './theme.js';
const App = () => {
const { settings } = useSettings();
const theme = createTheme(settings.theme);
return (
<ThemeProvider theme={theme}>
...
</ThemeProvider>
);
};
export default App;
...
That's all.
Now you can use the selected theme without conditional render in your components.
<div style={{ backgroundColor: theme.palette.primary }}></div>
But this will not prevent the selected theme after hard refresh.
So if you want to keep the theme as selected even after refresh your browser, then you could save the theme setting in the localStorage.

Apply a different theme on a per story basis storybook

I am making a library of react components in storybook (v6.1.14)
I have two different themes, basically and light and dark version.
I wrap all of my stories in the <ThemeProvider> like this:
import React from "react"
import {dark} from "../src/Themes/Theme";
import { ThemeProvider } from "styled-components";
import { GlobalStyles } from "../src/lib/Themes";
// apply our projects theme to our stories
const ThemeDecorator = storyFn => (
<ThemeProvider theme={dark}>
<GlobalStyles />
{storyFn()}
</ThemeProvider>
)
export default ThemeDecorator;
This is applied through the preview.js file like so:
const { addDecorator } = require("#storybook/react");
import ThemeDecorator from "./themeDecorator"
// Add our project theme, add a global stylesheet
addDecorator(ThemeDecorator);
So now everything has the dark theme applied, however, for each component I want to show two stories: One for the light theme and one for the dark theme - how do I make a story take on a different theme to the one globally declared?
I had the same problem as well. From the Storybook docs, I found this answer.
So basically, you can use decorators with your template and apply styling to it.
const Template: Story<LogoProps> = (args) => {
const { version } = args;
return <Logo {...args} version={version} />;
};
export const Light = Template.bind({});
Light.args = {
version: 'light',
};
Light.decorators = [
(LightLogo) => (
<div style={{ backgroundColor: 'black', padding: '5px 7px' }}>
<LightLogo />
</div>
),
];

Pass Styled component as className to another component

I am using react-paginate library that accepts class names in props to style internal components:
<ReactPaginate
...
breakClassName={'break-class-name'}
containerClassName={'pagination-class-name'}
activeClassName={'active-class-name'} />
I am also using styled-components, so I would like to avoid style react components using plain CSS or createGlobalStyle. Is there any way to pass styles from styled-components to breakClassName and containerClassName props of ReactPaginate?
I tried this, but it does not work, because Button.toString() returns class name without styles:
const Button = Styled.button`
color: green;
`
export default () => (
<ReactPaginate
...
breakClassName={Button.toString()} />
)
Code bellow also does not work, because Button has class name my-class-name, but this class name is without styles:
const Button = Styled.button.attrs({ className: 'my-class-name' })`
color: green;
`
export default () => (
<ReactPaginate
...
breakClassName='my-class-name' />
)
Is there any way to do that?
have you tried to make <Button as={Component} />?
UPDATE:
You can use wrapper with classes
const ReactPaginateStyled = styled(ReactPaginate)`
&.break-class-name {
//your styles
}
&.pagination-class-name {
//your styles
}
&.active-class-name {
//your styles
}
`;
I don't think there's a simple way to pass a className down like that. But if you're trying to style a component from a library, they should allow for this pattern to work:
const Button = styled(SomeLibraryComponent)`
color: green;
`;
Styled components will "wrap" around the base component and try to pass styles to it. Most library components should work with this but I can't speak for all of them.

Why is it impossible to style a success button in #material-ui

I am using https://material-ui.com/
My objective is to get a success Button, and Chip. Does anyone know how to do this without these hacky solutions: Material-ui #next customize button colors?
The goal isn't to create a styled component, the goal is to be able to use <Button color="success" /> or <Chip color="success" />.
I have tried <Box bgcolor="success.main">{ props => <Chip {...props} />}</Box> to no avail. I would be OK with using Box but not even that works.
It seems a bit ridiculous that these basic UI things are so cumbersome with this library. Success and Error colors should be a default, and they seem to be missing from every single component in this library. Am I missing something?
This is not a full/working example, but hopefully it provides some context. The Main component is included (eventually) via an AuthenticatedRoute within the Router shown in App.js. It doesn't really add anything to show all that complexity, but I wanted to give you some context.
Steps:
Create theme using createMuiTheme.
Add a ThemeProvider component with a theme prop that accepts your theme. This should be a parent to any Component that will make use of the theme, ours is in App.js.
Use the optional argument that makeStyles has to pass your theme into your Component (Main in my example). You can see examples where we access theme properties such as theme.palette.brand.primary.
Use the useStyles hook to create a variable in your component.
Apply those styles to your Component's elements via the className prop. For example, <div className={classes.root}>...</div>
Repeat 3-5 on any Component that needs styling.
I'm still learning my way around Material UI; we just recently migrated from react bootstrap to MUI. We still have some *.scss files laying around, but everything can co-exist. Our eventual plan is to move everything into the makeStyles hooks, but we haven't had time to refactor everything yet.
As a non-CSS guru, I much prefer to write the styling as code (even if I have to write some boilerplate) than to deal with .scss files and specificity/inheritance.
Theme.js:
import { createMuiTheme } from '#material-ui/core/styles';
export default createMuiTheme({
palette: {
brand: {
primary: '#1b3c6b',
primaryLight: '#adceff',
secondary: '#2a5da6',
tertiary: '#bf1e2e',
tertiaryLight: '#c35f69',
},
},
typography: {
color: '#333',
},
sidebar: {
header: {
background: '#2a5da6',
},
content: { background: '#fff' },
},
status: {
danger: 'orange',
},
});
Main.js (partial):
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
},
appBar: {
background: theme.palette.brand.primary,
boxShadow: 'none',
'& .MuiTabs-indicator': {
backgroundColor: theme.palette.brand.primaryLight,
height: 3,
},
position: 'fixed',
zIndex: 1000,
},
}));
const Main = React.memo(props => {
const classes = useStyles();
...
return (
<div className={classes.root}>
<AppBar className={classes.appBar} position="static">
....
</AppBar>
</div>
);
}
App.js (partial):
import { CssBaseline, ThemeProvider } from '#material-ui/core';
import theme from 'Theme/Theme'; // Theme.js from above
...
class App extends Component {
...
render() {
...
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Router>
...
</Router>
</ThemeProvider>
);
}
...
}

Using a styled component's rules within `createGlobalStyle`

New to styled components and am wondering if someone might have some advice on how to use a styled component's rules within a call to createGlobalStyle?
The below example is working, but I feel that it is not a great solution as componentStyle.rules is not in the official api docs.
// A styled component
import Modal from '../Modal'
import styled, { createGlobalStyle } from 'styled-components'
const StyledComponent = styled(Modal)`
background-color: pink;
`
createGlobalStyle`
// this div is mounted outside of the React root
.modal-from-external-library {
${StyledComponent.componentStyle.rules}
}
`
Not sure if what I was trying to do was possible, but I ended up solving the problem by exporting the css from the Modal using the css function of styled components.
// Modal.js
const styles = css`
// styles here
`
export default styled.div`
${styles}
`
// ... later
const GlobalStyles = createGlobalStyle`${styles}`
render() { return (<GlobalStyles {...props} />) }

Categories

Resources