What I would like to do is to compose multiple styled components into one.
With plain css this is very easy:
<button class="btn large red"></button>
This is my attempt with emotion in React:
import styled from "#emotion/styled/macro";
const A = styled.button({
color: "red"
});
const B = styled.button({
fontSize: 32
});
// I know how to add single styled component. But how to also add B here?
const C = styled(A)({
// some additional styles
});
function App() {
return (
<div className="App">
<A>A</A>
<B>B</B>
<C>C</C>
</div>
);
}
Please check the demo:
Demo
It seems like styled is not capable of combining multiple styled components by default.
You might want to look at the css functionality of emotion here. This allows the composition of multiple defined css styles. Even though this required more lines of code for the extra definition of the css objects.
Using css your example could look like this:
import styled from "#emotion/styled/macro";
import { css } from "#emotion/core";
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const red = css({
color: "red"
});
const A = styled.button(red);
const bolt = css({
fontSize: 32
});
const B = styled.button(bolt);
const C = styled.button(red, bolt);
function App() {
return (
<div className="App">
<A>A</A>
<B>B</B>
<C>C</C>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Demo
Related
can somebody explain how pass in the defauklt theme in Material UI5
in Material UI6 i use to do it like this
const useStyles = makeStyles((theme) => ({
home: {
display: "flex",
paddingTop: "8rem",
width: "100vw",
height: "100vh",
backgroundColor: theme.palette.primary.dark,
color: "white",
},
}));
but as i got throught M-UI5 docs (as far as i found) there is no explanation on how it can be done , the only part they mention about makeStyle it contains this code in this page docs
+import { makeStyles } from '#mui/styles';
+import { createTheme, ThemeProvider } from '#mui/material/styles';
+const theme = createTheme();
const useStyles = makeStyles((theme) => ({
background: theme.palette.primary.main,
}));
function Component() {
const classes = useStyles();
return <div className={classes.root} />
}
// In the root of your app
function App(props) {
- return <Component />;
+ return <ThemeProvider theme={theme}><Component {...props} /></ThemeProvider>;
}
so am i suppose to run createTheme() on every component to get the theme? , apology if i missed out an obvious thing in the docs , probably coz my poor english
The part you are missing is from this part of the migration guide: https://mui.com/material-ui/guides/migration-v4/#style-library.
if you are using JSS style overrides for your components (for example overrides created by makeStyles), you will need to take care of the CSS injection order. To do so, you need to have the StyledEngineProvider with the injectFirst option at the top of your component tree.
Without this, the default styles for the MUI Card in your About component win over the styles specified via classes.about where the styles overlap (e.g. background).
Changing your AllProviders component to the following (just adding <StyledEngineProvider injectFirst>) fixes it:
import React from "react";
import CountriesProvider from "./countries-context";
import QuestionsProvider from "./questions-context";
import {
ThemeProvider,
createTheme,
StyledEngineProvider
} from "#mui/material/styles";
const theme = createTheme();
const AllProviders = (props) => {
return (
<StyledEngineProvider injectFirst>
<QuestionsProvider>
<ThemeProvider theme={theme}>
<CountriesProvider>{props.children}</CountriesProvider>
</ThemeProvider>
</QuestionsProvider>
</StyledEngineProvider>
);
};
export default AllProviders;
https://codesandbox.io/s/funny-flower-w9dzen?file=/src/store/AllProviders.js:303-342
The theme was being passed in fine without this change (otherwise you would have had errors when it tried to access parts of the theme), but the CSS injection order was not correct.
I've created a icon.js file. It exports 3 SVG components to different files.
Like this :
export const MenuIcon = () => {
return (
<svg>
...
</svg>
);
};
export const ArrowLeftIcon = () => {
return (
<svg>
...
</svg>
);
};
export const SearchIcon = () => {
return (
<svg>
...
</svg>
);
};
And I would like to apply the same styles to all three of these components using styled components, not giving them a same className. Is there a solution for that? Thank you :)
I think the following steps could help you.
Steps
npm i styled-components#4.1.3
import styled from 'styled-components';
const StyledSvg = styled.svg` <---the html tag that you want to style
color: palevioletred; <---the css styles that you want to use
`;
Now use StyledSvg that you just created instead of the svg tags.
return(
<StyledSvg>
...
</StyledSvg>
);
What I want:
I'm trying to add a dynamic theme option to a react-styleguidist project I'm working on. Following the idea laid out in this unfinished and closed pr, I added a custom ThemeSwitcher component, which is a select menu that is rendered in the table of contents sidebar. Selecting an option should update the brand context, which renders the corresponding theme using styled-components' BrandProvider. It should function like the demo included with the closed pr: https://fancy-sg.surge.sh/.
What's not working:
I can't access the same context in my ThemedWrapper as is provided and updated in the StyleguideWrapper and ThemeSwitcher. Examining the tree in the React Components console, it looks like react-styleguidist may render ReactExample outside of the StyleguideRenderer, which means it loses the context from the provider in that component.
Assuming I'm correct about the context not updating in ThemedWrapper due to it being located outside of StyleGuideRenderer, two high level ideas I have (but haven't been able to figure out how to do) are:
Find the correct component that is an ancestor of both StyleGuideRenderer and ReactExample in the react-styleguidist library and add the BrandProvider there so that ThemedWrapper now has context access
Some other context configuration that I haven't found yet that will allow two components to consume the same context without having a provider as an ancestor (is this possible??)
What I have:
Here are the condensed versions of the relevant code I'm using.
brand-context.js (exports context and provider, inspired by Kent C Dodds
import React, { createContext, useState, useContext } from 'react';
const BrandStateContext = createContext();
const BrandSetContext = createContext();
function BrandProvider({ children, theme }) {
const [brand, setBrand] = useState(theme);
return (
<BrandStateContext.Provider value={brand}>
<BrandSetContext.Provider value={(val) => setBrand(val)}>
{children}
</BrandSetContext.Provider>
</BrandStateContext.Provider>
);
}
function useBrandState() {
return useContext(BrandStateContext);
}
function useBrandSet() {
return useContext(BrandSetContext);
}
export { BrandProvider, useBrandState, useBrandSet };
StyleGuideWrapper.jsx (Copy of rsg-components/StyleguideRenderer, with addition of ThemeSwitcher component to toggle theme from ui; passed in styleguide config as StyleGuideRenderer)
import React from 'react';
import cx from 'clsx';
import Styled from 'rsg-components/Styled';
import ThemeSwitcher from './ThemeSwitcher';
import { BrandProvider } from './brand-context';
export function StyleGuideRenderer({ children, classes, hasSidebar, toc }) {
return (
<BrandProvider>
<div className={cx(classes.root, hasSidebar && classes.hasSidebar)}>
<main className={classes.content}>
{children}
</main>
{hasSidebar && (
<div className={classes.sidebar} data-testid="sidebar">
<section className={classes.sidebarSection}>
<ThemeSwitcher classes={classes} />
</section>
{toc}
</div>
)}
</div>
</BrandProvider>
);
}
StyleGuideRenderer.propTypes = propTypes;
export default Styled(styles)(StyleGuideRenderer);
ThemeSwitcher.jsx
import React from 'react';
import Styled from 'rsg-components/Styled';
import { useBrandSet, useBrandState } from './brand-context';
const ThemeSwitcher = ({ classes }) => {
const brand = useBrandState();
const setBrand = useBrandSet();
const onBrandChange = (e) => setBrand(e.target.value);
const brands = ['foo', 'bar'];
return (
<label className={classes.root}>
Brand
<select value={brand} onChange={onBrandChange}>
{brands.map((brand) => (
<option key={brand} value={brand}>{brand}</option>
))}
</select>
</label>
);
};
export default Styled(styles)(ThemeSwitcher);
ThemedWrapper.jsx (passed in styleguide config as Wrapper, and wraps each example component to provide them to styled-components)
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { BrandStateContext } from './brand-context';
const LibraryProvider = ({ brand, children }) => {
return (
<ThemeProvider theme={brand}>{children}</ThemeProvider>
);
};
function ThemedWrapper({ children }) {
return (
<BrandStateContext.Consumer>
{brand => (
<LibraryProvider brand={brand}>{children}</LibraryProvider>
)}
</BrandStateContext.Consumer>
);
}
export default ThemedWrapper;
I have created a Dropdown Component in React using Styled Components. Here is a simplified outline of the component:
const Dropdown = (
<DropdownBase>
<Trigger>
{title}
</Trigger>
<Submenu>
{children}
</Submenu>
</DropdownBase>
)
const DropdownBase = styled.div`
/* Default Styles */
`
const Trigger = styled(Link)`
/* Default Styles */
`
const Submenu = styled.div`
/* Default Styles */
`
Now, when I import and use the component I want to be able to override the default styles of the nested components (i.e., DropdownBase, Trigger and Submenu). And I want to be able to override those default styles using Styled Components. The problem is, that I do not import those nested components -- I only import the Dropdown component -- like this:
import { Dropdown } from '../path/to/dropdown'
<Dropdown />
So I am wondering, how can I override those nested components when I import the parent component using Styled Components?
The best way to do this would be to export DropdownBase, Trigger, and Submenu from your Dropdown component, then import them along with Dropdown and override this like this:
import { Dropdown, DropdownBase, Trigger, Submenu } from '../path/to/dropdown'
import styled from 'styled-components'
const MyComponent = () => {
return <StyledDropdown />
}
const StyledDropdown = styled(Dropdown)`
${DropdownBase} {
// custom styles
}
${Trigger} {
// custom styles
}
${Submenu} {
// custom styles
}
`
This works well because it targets the specific child styled components.
Alternatively, you could target them based on their tag or child order, but this may fail if you make updates to the Dropdown component.
How about this:
const Dropdown = (
<DropdownBase className={dropdownBaseClassName}>
<Trigger className={triggerClassName}>
{title}
</Trigger>
<Submenu className={submenuClassName}>
{children}
</Submenu>
</DropdownBase>
)
import { Dropdown } from '../path/to/dropdown'
<StyledDropdown />
const StyledDropdown = styled(Dropdown).attrs({ dropdownBaseClassName:..., triggerClassName:..., submenuClassName:... })`
.${dropdownBaseClassName} {
// styles
}
.${triggerClassName} {
// styles
}
.${submenuClassName} {
// styles
}
Extending the answer by #Appel21, I would do something like this using dot notation:
import styled from 'styled-components'
export const Dropdown = () => (
<DropdownBase>
<Trigger>
{title}
</Trigger>
<Submenu>
{children}
</Submenu>
</DropdownBase>
)
const DropdownBase = styled.div`
/* Default Styles */
`
const Trigger = styled(Link)`
/* Default Styles */
`
const Submenu = styled.div`
/* Default Styles */
`
Dropdown.base = DropdownBase;
Dropdown.trigger = Trigger;
Dropdown.subMenu = Submenu;
And then to use it:
import { Dropdown } from '../path/to/dropdown'
import styled from 'styled-components'
const MyComponent = () => {
return <StyledDropdown />
}
const StyledDropdown = styled(Dropdown)`
${Dropdown.base} {
// custom styles
}
${Dropdown.trigger} {
// custom styles
}
${Dropdown.submenu} {
// custom styles
}
`
This way you have to export only one component and you get autocompletion of the subcomponents, knowing perfectly what subcomponents you can style :)
It looks like themes are what you want.
import { render } from "react-dom"
import { ThemeProvider } from "styled-components"
const Dropdown = (
<DropdownBase>
<Trigger>
{title}
</Trigger>
<Submenu>
{children}
</Submenu>
</DropdownBase>
)
const defaultTheme = {color:'black'}
const specificTheme = {color:'red'}
const DropdownBase = styled.div`
/* Default Styles */
color:${props=>props.theme.color};
`
const Trigger = styled(Link)`
/* Default Styles */
color:${props=>props.theme.color};
`
const Submenu = styled.div`
/* Default Styles */
color:${props=>props.theme.color};
`
render(<ThemeProvider theme={defaultTheme}>
<div>
<Dropdown>Your default dropdown</Dropdown>
<div>
Your hierarchy
<ThemeProvider theme={specificTheme}>
<Dropdown>Your custom dropdown</Dropdown>
</ThemeProvider>
</div>
</div>
</ThemeProvider>)
I have a simple React JS component that wraps around the really cool react ChartistGraph component. The only issue is that the styling is seemingly overridden by the ChartistGraph default CSS. There is a lot of info on the regular Chartist js package but not much on the React JS package.
As you can see, I'm trying to change the fill color two ways: through style classes and through a prop that supported on the component.
import React from 'react';
import { Paper, withStyles } from 'material-ui';
import ChartistGraph from 'react-chartist';
const styles = theme => ({
graphStyle: {
fill: 'red',
},
});
const CustomChart = ({ classes, graph }) => {
return (
<Paper>
<ChartistGraph
className={classes.graphStyle}
data={graph.data}
options={graph.options}
type={graph.type}
style={{ fill: 'red' }}
/>
</Paper>
);
};
export default withStyles(styles)(CustomChart);
A picture of the styles of the chart
You can use jss's nested rules (included by default in material-ui):
const styles = theme => ({
graphStyle: {
'& .ct-label': { fill: 'red' },
},
});
Full code:
import React from 'react';
import { Paper, withStyles } from 'material-ui';
import ChartistGraph from 'react-chartist';
const styles = theme => ({
graphStyle: {
'& .ct-label': { fill: 'red' },
},
});
const CustomChart = ({ classes, graph }) => {
return (
<Paper>
<ChartistGraph
className={classes.graphStyle}
data={graph.data}
options={graph.options}
type={graph.type}
// style={{ fill: 'red' }} // omitted
/>
</Paper>
);
};
export default withStyles(styles)(CustomChart);
I got into similar issue recently.React-Chartist is built on top of react not material-ui.When you inspect,you found regular css class names,not "material ui-ish" class-names(like MuiTable-root,MuiTable-selectedRow,etc).So ,imho,it won't support material-ui methods (withStyle/makeStyle) and rules.
But what you can do is:-
create a css file and put your styles there
And import it where you want
.You can import it on the main file of your app(index.js or whatever it is) since every css in your app will bundle in one file.