I've been working on pulling our existing react FE components out of our main repo, and into a separate repo that I am bundling with rollup. Our old code was using makeStyles and I have been switching that over to styled-components but still keeping the previous MUI theme. I've setup storybook and am wrapping that in styled components theme provider, in order to access the theme inside the styled components.
The structure looks like
components
\src
index.ts(imports and exports components)
\theme(MUI theme)
\components
\buttons
button.tsx(react button code)
index.ts(imports and exports button)
\lib(rollup spits this out)
Finally, to the question. After I bundle everything with rollup, I do an NPM install, and import it into a different project. The problem is, I'm not getting the proper theming in the imported components. Here is a somewhat simplified version of my button.
import React from "react";
import { Button as MaterialButton, ButtonProps } from "#material-ui/core";
import styled from "styled-components";
export interface MyButtonProps extends ButtonProps {
error?: boolean;
target?: string;
}
const StyledButton = styled(MaterialButton)`
&.error {
background: ${(props) => props.theme.palette.error.main};
color: #fff;
&:hover {
background: ${(props) => props.theme.palette.error.main};
}
}
`;
const Button = ({
error,
className,
...rest}: MyButtonProps) => {
className += error ? " error" : "";
return (
<StyledButton
{...rest}
className={className}
>
{children}
</StyledButton>
);
};
export default Button;
So, if I put error attribute on the button, I do get the correct color from my theme. However, if I put color="primary" I do not get the correct color. I also don't have any of my base styles from the theme.
I haven't been able to figure out how to get this theme into the components I'm bundling with rollup. Finally, here is my rollup config.
import peerDepsExternal from "rollup-plugin-peer-deps-external";
import resolve from "#rollup/plugin-node-resolve";
import commonjs from "#rollup/plugin-commonjs";
import typescript from "rollup-plugin-typescript2";
import postcss from "rollup-plugin-postcss";
import svg from "rollup-plugin-svg-import";
const packageJson = require("./package.json");
export default {
input: "src/index.ts",
output: [
{
file: packageJson.main,
format: "cjs",
sourcemap: true,
},
{
file: packageJson.module,
format: "esm",
sourcemap: true,
},
],
plugins: [
peerDepsExternal(),
resolve(),
commonjs(),
svg(),
typescript({ useTsconfigDeclarationDir: true }),
postcss({
extensions: [".css"],
}),
],
};
For anyone who has stumbled upon this and is curious about the answer. Here is what I ended up doing. Hopefully this is the correct solution. One thing that is a bummer, I end up with MUI ThemeProvider giving me it's generated class names. This means in my styled components, I'll occasionally need to do this to target things [class^="MuiSvgIcon-root"], but maybe i can access the class through props, still messing with it. Anyway, here is the button.
import React from "react";
import { Button as MaterialButton, ButtonProps } from "#material-ui/core";
import styled from "styled-components";
import { ThemeProvider } from "#material-ui/core/styles";
import theme from "../../../theme";
export interface MyButtonProps extends ButtonProps {
error?: boolean;
target?: string;
}
const StyledButton = styled(MaterialButton)`
&.error {
background: ${theme.palette.error.main};
color: #fff;
&:hover {
background: ${theme.palette.error.main};
}
}
`;
const Button = ({
error,
size,
variant,
endIcon,
children,
className,
...rest
}: MyButtonProps) => {
if (className && error) {
className += " error";
} else if (!className && error) {
className = "error";
}
return (
<ThemeProvider theme={theme}>
<StyledButton
{...rest}
variant={variant}
size={size}
endIcon={endIcon}
className={className}
>
{children}
</StyledButton>
</ThemeProvider>
);
};
export default Button;
I guess each component will need to be wrapped.
Related
From the Material UI website, there seem to be multiple ways of theming, and I'm not sure which to use.
There's this method, which just seems to use pure React context. However, I don't see how main relates to the color. I'm not sure how it is a field that Checkbox is able to extract from, given we don't specify it in the HTML.
import * as React from 'react';
import { createTheme, ThemeProvider } from '#mui/material/styles';
import Checkbox from '#mui/material/Checkbox';
import { green, orange } from '#mui/material/colors';
const outerTheme = createTheme({
palette: {
primary: {
main: orange[500],
},
},
});
const innerTheme = createTheme({
palette: {
primary: {
main: green[500],
},
},
});
export default function ThemeNesting() {
return (
<ThemeProvider theme={outerTheme}>
<Checkbox defaultChecked />
<ThemeProvider theme={innerTheme}>
<Checkbox defaultChecked />
</ThemeProvider>
</ThemeProvider>
);
}
There's this method, which seems to make more sense. Here we explicitly extract the theme color via theme.status.danger and place it into the color field.
import * as React from 'react';
import Checkbox from '#mui/material/Checkbox';
import { createTheme, ThemeProvider, styled } from '#mui/material/styles';
import { orange } from '#mui/material/colors';
declare module '#mui/material/styles' {
interface Theme {
status: {
danger: string;
};
}
// allow configuration using `createTheme`
interface ThemeOptions {
status?: {
danger?: string;
};
}
}
const CustomCheckbox = styled(Checkbox)(({ theme }) => ({
color: theme.status.danger,
'&.Mui-checked': {
color: theme.status.danger,
},
}));
const theme = createTheme({
status: {
danger: orange[500],
},
});
export default function CustomStyles() {
return (
<ThemeProvider theme={theme}>
<CustomCheckbox defaultChecked />
</ThemeProvider>
);
}
I could also change the MUI nested component:
import * as React from 'react';
import { ThemeProvider, createTheme } from '#mui/material/styles';
import Button from '#mui/material/Button';
const theme = createTheme({
components: {
MuiButton: {
styleOverrides: {
root: {
fontSize: '1rem',
},
},
},
},
});
export default function GlobalThemeOverride() {
return (
<ThemeProvider theme={theme}>
<Button>font-size: 1rem</Button>
</ThemeProvider>
);
}
Which is the right way to theme?
Good evening, I've recently started using UI stuff and from what I've seen in version 5, the theme as you mentioned is used. Another way I'm styling it is using the SX props on the element.
https://mui.com/pt/system/the-sx-prop/
I spend a few days trying to customize the primary color and add two more colors to the palette. I was able to declare properly the new colors...but at the moment to see those new colors reflected on the button doesnt work. The button are taking the default properties even when I wrapped under the Themeprovider. I'm using storybook.
import React from "react";
import { Meta } from "#storybook/react/types-6-0";
import { Button } from "#mui/material";
import { createTheme, ThemeProvider, styled } from '#mui/material/styles';
const theme = createTheme({
palette: {
primary: {
contrastText: "#ff0000",
light: "#ff0000",
main: "#ff0000",
dark: "#ff0000"
},
tertiary: {
main: "#ff0000"
},
failure: {
main: "#ff0000"
}
}
});
const CustomStyles = () => {
return (
<ThemeProvider theme={theme}>
<Button variant="contained">Click me</Button>
</ThemeProvider>
);
}
const Template = () => {
return (
<CustomStyles />
);
};
export const Default = Template.bind({});
export default {
title: "mylib/Theme"
} as Meta;
This is how it looks
default button style
Themeprovider custom palette
As you may see, the ThemeProvider has the palette color definition...but some how the button doesnt take it.
Thanks in advance
Adding this to.storybook/preview.js was enough to solve my case. Follow the official migration guide on this matter to learn more.
//.storybook/preview.js
import { ThemeProvider } from '#mui/material/styles';
import { ThemeProvider as Emotion10ThemeProvider } from 'emotion-theming';
import { theme } from '../your/system/customTheme/path';
const defaultTheme = theme;
const withThemeProvider = (Story, context) => {
return (
<Emotion10ThemeProvider theme={defaultTheme}>
<ThemeProvider theme={defaultTheme}>
<Story {...context} />
</ThemeProvider>
</Emotion10ThemeProvider>
);
};
export const decorators = [withThemeProvider];
//another storybook exports.
EDIT: this issue seems to be related to stackoverflow.com/a/70254078/17724218 as OP commented below.
I'm trying to use Storybook 6.1.20 for a very simple component in my project which uses Next.js, React and Typescript, however Storybook does not apply the styles that I have defined in my .module.css file. The styles are however applied when I start the project itself via npm run dev
This is my component that I want to display in Storybook (Rating.tsx):
import React from "react"
import ratingStyles from '../../../styles/Rating.module.css'
export interface RatingProps {
value: number
}
const Rating: React.FC<RatingProps> = ({value}) => {
return (
<span className={ratingStyles.text}>
{value}
</span>
)
}
export default Rating
The css file (Rating.module.css):
.text {
font-weight: 600;
font-size: 200px;
}
The story file (Rating.stories.tsx)
import React from 'react'
import { Story, Meta } from '#storybook/react';
import Rating, { RatingProps } from '../components/generic/Rating'
export default {
title: 'Rating',
component: Rating,
} as Meta
const Template: Story<RatingProps> = (args) => <Rating {...args}/>
export const FirstStory = Template.bind({})
FirstStory.args = {
value: 10
}
i was trying storybook and chakra ui.
Down below is how I structured my folders,
module.exports = {
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.#(js|jsx|ts|tsx)"
],
"addons": [
"#storybook/addon-links",
"#storybook/addon-essentials",
"#storybook/preset-create-react-app"
]
}
and this is my storybook config.
unluckly i receive an error, it seems like storybook doesn't find my components.
can someone help me to understand what's wrong ?
Why storybook doesn't find my components ?
-----------Update------------
this is the story that I tried to added
import React from 'react';
import {
Menu,
MenuButton,
} from '#chakra-ui/core';
export function MenuComp() {
return (
<Menu>
<MenuButton>Games</MenuButton>
</Menu>
);
}
MenuComp.storyName = 'test';
I started with an easier component and followed strictly the tutorial
i will wrote here a little guide that, I hope, will simplify the reader life.
1.I wrote a simple component in path "src/components/CustomButton"
import React, { FC } from 'react';
import { Button } from '#chakra-ui/core';
interface ButtonProps {
size: string | any;
variantColor: string;
}
const CustomButton: FC<ButtonProps> = ({ size, variantColor }) => (
<Button variantColor={variantColor} size={size}>
Button
</Button>
);
export default CustomButton;
2.I added the relative story component in "src/stories/CustomButton.stories.tsx
import React from 'react';
import CustomButton from 'components/CustomButton/index';
export default {
component: CustomButton,
title: 'CustomButton',
};
const Template = (args:any) => <CustomButton {...args} />;
export const Default: any = Template.bind({});
Default.args = {
size: 'md',
variantColor: 'green',
};
3.At this point it's really important configure your file preview.js that you will find the folder ".storybook", thanks to this commit chakraUiCommit i figured out how to include the theme provider in every storybook component
import React from 'react'
import {addDecorator} from '#storybook/react'
import {ThemeProvider, CSSReset} from '#chakra-ui/core'
addDecorator((storyFn) => (
<ThemeProvider>
<CSSReset />
{storyFn()}
</ThemeProvider>
))
well, at this point i solved my problem, and this is my final result
I am trying to apply a global border-radius: 0px to my web application which uses Ant Design. So far, most of the components seem to adhere to the less styling applied (as shown in the code snippet below).
However, for some reason the Input component from antd does not seem to respect this.
BasicInput.tsx
import React, { ReactNode } from 'react';
import { Input } from 'antd';
interface BasicInputProps {}
export const BasicInput: React.FC<BasicInputProps> = ({}) => {
return <Input />; // Return the ant design component untouched.
};
LargeButton.tsx
import React, { ReactNode } from 'react';
import { Button } from 'antd';
import { PG } from '../typography/PG';
interface LargeButtonProps {
children?: ReactNode;
icon: string;
type?:
| 'link'
| 'default'
| 'ghost'
| 'primary'
| 'dashed'
| 'danger'
| undefined;
onClick?: (event: React.MouseEvent) => void;
}
export const LargeButton: React.FC<LargeButtonProps> = ({
children,
type,
icon,
...rest
}) => {
return (
<Button type={type} size='large' icon={icon} {...rest}>
{children}
</Button>
);
};
input.stories.js
import React, { useState } from 'react';
import { BasicInput } from './BasicInput';
import { LargeButton } from '../buttons/LargeButton';
import { Spacer, Padding } from '../../config/styledConstants';
export default {
title: 'Input'
};
export const sampleInput = () => (
<Padding>
<LargeButton></LargeButton>
<BasicInput placeholder='Oh peach pit where had the hours gone...' />
</Padding>
);
webpack.config.js
...
{
test: /\.less$/,
loaders: [
'style-loader',
'css-loader',
{
loader: 'less-loader',
options: {
modifyVars: {
'#border-radius-base': '0px',
'#border-radius-sm': '0px',
'#font-family': 'Oswald, sans-serif',
'#black': '#1B2021',
'#white': '#F2F2F2'
},
javascriptEnabled: true
}
}
],
include: path.resolve(__dirname, '../')
}
...
As you can see in the screenshot from storybook, the button has no border radius while the input component still has a radius. This is despite having having override the less styling.
Strangely, if I directly import the core component into the stories, the border radius disappears:
Workaround by directly importing into stories
import React, { useState } from 'react';
import { BasicInput } from './BasicInput';
import { Input } from 'antd'; // IMPORT DIRECTLY.
import { Spacer, Padding } from '../../config/styledConstants';
export default {
title: 'Input'
};
export const sampleInput = () => (
<Padding>
<Input /> // THIS WORKS.
</Padding>
);
Is there a bug or am I doing something wrong?