use material UI makeStyles with Typescript - reactjs

I have created a separate file for classes prop for e.g. MuiAlert
What is the way to tell makeStyles that you are only allowed to use Alert classes?
The following works but I am sure there must be a better way. So e.g. If I rename root to roott, I will get error that 'roott' does not exist in type 'Partial<Record<AlertClassKey, any>>'
Playground example: https://codesandbox.io/s/typescript-react-material-ui-3t7ln?file=/src/index.ts
import { Theme, makeStyles } from "#material-ui/core";
import { AlertClassKey } from "#material-ui/lab/Alert";
export const useAlertClasses = makeStyles<Theme>(
(): Partial<Record<AlertClassKey, any>> => ({
root: {
borderRadius: 3,
}
}));

my solution:
import { Theme } from "#material-ui/core/styles/createMuiTheme";
import { StyleRules } from "#material-ui/core/styles/withStyles";
import { makeStyles } from "#material-ui/core/styles";
import { createStyles } from "#material-ui/core/styles";
import { AlertClassKey } from "#material-ui/lab/Alert";
export const alertOverrides = (
theme: Theme
): Partial<StyleRules<AlertClassKey>> => {
return {
root: {
backgroundColor: "red !important",
},
};
};
export const useAlertStyles = makeStyles<Theme, {}>(
(theme) => {
return createStyles(alertOverrides(theme) as never);
},
{ name: "MuiAlert" }
);

Related

Create wrapper for translation with MUI - WEBPACK_IMPORTED_MODULE_16__ is undefined

I use react-i18next in my project to provide proper translations, in most components I import
const { t } = useTranslation();
to provide proper content however I wanted to write custom translationWrapper where I would use useTranslation hook and just reuse that component where I need. However it keeps throwing error:
Uncaught TypeError: TranslationWrapper__WEBPACK_IMPORTED_MODULE_16_
is undefined
Why does it happen? Interesting is that if project is running and I add it then it works correctly but when I refresh browser then get above error
My Custom wrapper:
import React, { FC } from 'react';
import { Typography, TypographyProps } from '#mui/material';
import { TFunction } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import { styled } from '#mui/styles';
import { TOptions } from 'i18next';
export type TextContentPath = Parameters<TFunction<'translation'>>[0];
export interface TranslationWrapperProps extends TypographyProps {
content?: TextContentPath;
tParams?: TOptions<{}>;
onClick?: () => void;
}
const TranslationWrapperComponent: FC<TranslationWrapperProps> = ({ content, tParams, onClick, ...materialProps }) => {
const { t } = useTranslation();
return (
<Typography {...materialProps} {...onClick}>
{t(content as TemplateStringsArray, tParams)}
</Typography>
);
};
export const TranslationWrapper = React.memo(TranslationWrapperComponent) as typeof TranslationWrapperComponent;
and try to reuse it here:
export const MyComponent: React.FC = () => {
return (
<StyledSwitchBox>
<StyledTester
variant="p"
sx={{ cursor: 'pointer' }}
onClick={() => console.log('test')}
content={'myComponent.switchContent'}
/>
</StyledSwitchBox>
);
};
const StyledTester = styled(TranslationWrapper)({});

How do I correctly style a component in fluent UI/React/ts?

I am trying to add a background color to this div:
import * as React from 'react'
import { Itest } from '../../../../../models'
import { getPreviewStyles } from './preview.style.ts'
type IPreviewProps = Itest
const PreviewC: React.FunctionComponent<IPreviewProps> = (props: IPreviewProps) => {
console.log(props)
return (
<div>
<h1 className="base">test test</h1>
<p>testing testing</p>
<img src="workingAtComputer.jpg"></img>
</div>
)
}
export const Preview = PreviewC
Here I am trying to add a background color of purple:
import { IStyle } from 'office-ui-fabric-react'
export interface IPreviewStyles {
base: IStyle
}
export const getPreviewStyles = (): IPreviewStyles => {
return {
base: {
backgroundColor: 'purple',
}
}
I get this error in the first file: "'getPreviewStyles' is declared but its value is never read." How can I have the styling applied to the div with className "base"? Please assist. Thanks.
You probably want to implement css-in-js but for that you need to use Merge styles which comes along side with Office Fabric UI or FluentUI as newer version.
Preview.style.ts:
import { mergeStyleSets } from '#fluentui/merge-styles';
export interface IPreviewStyles {
base: string;
}
export const getPreviewStyles = (): IPreviewStyles => {
return mergeStyleSets({
base: {
background: 'purple',
},
});
};
Component:
import * as React from 'react'
import { Itest } from '../../../../../models'
import { getPreviewStyles } from './preview.style.ts'
type IPreviewProps = Itest
const PreviewC: React.FunctionComponent<IPreviewProps> = (props: IPreviewProps) => {
const { base } = getPreviewStyles()
return (
<div>
<h1 className={base}>test test</h1>
<p>testing testing</p>
<img src="workingAtComputer.jpg"></img>
</div>
)
}
export const Preview = PreviewC
You can read more about Merge styles API here. And my recommendation for you is to switch on latest version of FluentUI.

Styled components with React context

I want to use normal React context with styled-components. I am aware of generic theme providers in styled-components, however, it will be much better if I will be able to do this by using React.useContext(). Do you know is it possible? I tried something like this.
useConfig
export function useConfig() {
const configCtx = useContext<ConfigContextShape | undefined>(ConfigContext)
return configCtx
}
example styled.ts
import { useConfig } from 'state'
import styled from 'styled-components'
const { background_color } = useConfig()
export const VisualSearchWrapper = styled.main`
color: black;
background-color: ${background_color};
`
I will be appreciated for any help.
One more update:
In the example this issue occurred:
Uncaught TypeError: Cannot read properties of undefined (reading 'context')
The ConfigContextProvider is below
import { createContext } from 'preact'
import { FC } from 'preact/compat'
import { useContext, useEffect, useState } from 'preact/hooks'
export interface ConfigProfile {
background_color: string
}
interface ConfigFunctions {
updateConfigState: (updatedConfig: ConfigProfile) => void
}
type ConfigContextShape = ConfigProfile & ConfigFunctions
export const ConfigContext = createContext<ConfigContextShape | undefined>(undefined)
export const ConfigContextProvider: FC = ({ children }) => {
const [localState, setLocalState] = useState<ConfigProfile>({
background_color: '',
})
function updateConfigState(updatedConfig: ConfigProfile) {
setLocalState({ ...localState, ...updatedConfig })
}
const ctxValue: ConfigContextShape = {
background_color: localState?.background_color ?? '',
updateConfigState
}
return <ConfigContext.Provider value={ctxValue}>{children}</ConfigContext.Provider>
}
export function useConfig() {
const configCtx = useContext<ConfigContextShape>(ConfigContext)
if (configCtx === undefined) {
throw new Error('useConfig must be used within a ConfigProvider')
}
return configCtx
}

React: How to place Storybook .stories in each component's directory?

Currently all my .stories files are held within a stories directory. As I add more components, this will grow.
Instead I want to add each component's .stories file at the component level, as per the documentation on the Storybook site here: https://storybook.js.org/docs/basics/writing-stories/#loading-stories-dynamically
Thing is I don't know how to actually do this.
I have set up the following simple component (all files within same directory):
PageTitle.tsx
import React from 'react'
import { Typography } from '#material-ui/core';
import { makeStyles } from "#material-ui/styles";
const useStyles = makeStyles(() => ({
typographyStyles: {
flex: 1
}
}));
interface PageTitleProps {
title: string;
}
const PageTitle = ({ title }: PageTitleProps) => {
const classes = useStyles()
return (
<Typography className={classes.typographyStyles} variant="h1" component="h2">
{title}
</Typography>
)
};
export default PageTitle;
index.tsx
import PageTitle from './PageTitle';
export default PageTitle;
index.stories.tsx
import React from 'react';
import { storiesOf } from '#storybook/react'
import FormPageTitle from './index';
export default {
title: 'Titles|PageTitle',
component: PageTitle,
};
export const withText = () => {
return <PageTitle title="Hello World" />;
};
In my .storybook directory I have a main.js file that simply has:
module.exports = {
stories: ['../src/**/*.stories.tsx'],
addons: [
'#storybook/preset-create-react-app',
'#storybook/addon-actions',
'#storybook/addon-links',
{
name: '#storybook/addon-docs',
options: {
configureJSX: true,
},
},
],
};
But I just get the error:
Module not found: Error: Can't resolve './index' in
'/myApp/src/stories'
This occurs no matter what I call the .stories file.
Would anyone know the correct way to set this up?
Create a config.js inside your .storybook directory
With
import React from 'react';
import { configure, addDecorator } from '#storybook/react';
import { action } from '#storybook/addon-actions';
import styled from 'styled-components';
const Wrapper = styled.div`
display: flex;
flex: 1;
height: 100vh;
align-items: center;
justify-content: center;
`;
const Decorator = (storyFn) => <Wrapper>{storyFn()}</Wrapper>;
addDecorator(Decorator);
global.___loader = {
enqueue: () => {},
hovering: () => {},
};
global.__PATH_PREFIX__ = '';
window.___navigate = (pathname) => {
action('NavigateTo:')(pathname);
};
const req = require.context('../src/components/', true, /\.stories\.js$/);
function loadStories() {
req.keys().forEach((filename) => req(filename));
}
configure(loadStories, module);

How to pass material-ui's theme object to defaultProps?

In React, I have a functional component that validates props and implements default props for any non-required props. I'm also using mui's makeStyles to grab the theme object to apply styling to my components.
My question is how does one go about passing the makeStyles theme object down to the defaultProps to avoid hard keying values?
import React from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles(theme => ({
componentStyle: {
color: `${theme.palette.light.main}`, // just like how I'm accessing `theme` here, I'd like to access in `defaultProps`
},
componentContainer: ({ backgroundColor }) => {
return { backgroundColor };
},
}));
const Example = ({ backgroundColor }) => {
const classes = useStyles({ backgroundColor });
return (
<div className={classes.componentStyle} >
<div className={classes.componentContainer} /> // use default styling using `theme` if none is provided
</div>
)
}
Example.propTypes = {
backgroundColor: PropTypes.string,
};
Example.defaultProps = {
backgroundColor: `${theme.palette.light.main}`, // I want to access `theme` here and do the following. While `backgroundColor: 'white'` will work I want to avoid hard keying values.
};
export default Example;
EDIT: based on the solution provided by #Fraction below is what I'll move forward with.
import React from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles(theme => ({
componentStyle: {
color: `${theme.palette.light.main}`,
},
componentContainer: ({ backgroundColor }) => {
return {
backgroundColor: backgroundColor || `${theme.palette.light.main}`
};
},
}));
const Example = ({ backgroundColor }) => {
const classes = useStyles({ backgroundColor });
return (
<div className={classes.componentStyle} >
<div className={classes.componentContainer} />
</div>
)
}
Example.propTypes = {
backgroundColor: PropTypes.string,
};
Example.defaultProps = {
backgroundColor: null,
};
export default Example;
I would suggest to not pass theme as prop, but to use Theme context.
I do that in all apps which I am working on and it is flexible and prevents props drilling as well.
In your top level component, e.g. App.tsx put the Material UI theme provider:
import { ThemeProvider } from '#material-ui/core/styles';
import DeepChild from './my_components/DeepChild';
const theme = {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
};
function Theming() {
return (
<ThemeProvider theme={theme}>
<DeepChild />
</ThemeProvider>
);
}
Then, in your components which need a theme:
(as per https://material-ui.com/styles/advanced/#accessing-the-theme-in-a-component):
import { useTheme } from '#material-ui/core/styles';
function DeepChild() {
const theme = useTheme();
return <span>{`spacing ${theme.spacing}`}</span>;
}
You don't need to pass the makeStyles's theme object down to the defaultProps, just use the Logical OR || to set the backgroundColor property to theme.palette.light.main when the passed argument is any falsy value, e.g: (0, '', NaN, null, undefined):
const useStyles = makeStyles(theme => ({
componentStyle: {
color: `${theme.palette.light.main}`, // just like how I'm accessing `theme` here, I'd like to access in `defaultProps`
},
componentContainer: ({ backgroundColor }) => ({
backgroundColor: backgroundColor || theme.palette.light.main,
}),
}));

Resources