How can I add a global control matcher in Storybook? - reactjs

I am building a component library in React and TypeScript using Storybook. Many components have an icon prop. In the code snippet below, you can see a Component story example where I customize the icon prop's control using a select type with icon options.
// Component.stories.js
import { Component } from './component';
import { iconOptions, DefaultIcon } from "./icons";
export default {
component: Component,
title: 'Component',
argTypes: {
icon: {
control: {
type: 'select'
},
options: iconOptions,
defaultValue: DefaultIcon
}
}
};
The problem is that this setup is repetitive for all components using icon prop. Instead, I would like to configure this globally for all components. So any prop called icon would automatically get a select control with icon options.
I tried editing the .storybook/preview.js file:
export const parameters = {
controls: {
icon: {
control: {
type: 'select'
},
options: iconOptions,
defaultValue: DefaultIcon,
}
}
};
However, that did not work.
How can I configure such a global control?

Related

Extend MUI components from theme provider

Currently I'm wondering about if there is the possibility of extending components, by adding new props directly from the theme provider.
For example, let's take the IconButton component, I want to add square prop to this component
import { Components, Theme } from '#mui/material';
export const MuiIconButton: Components<Theme>['MuiIconButton'] = {
defaultProps: {
square: true
},
styleOverrides: {
root: ({ ownerState }) => ({
//here I would like to access the square prop
borderRadius: ownerState.square ? 0 : 50,
})
}
};
declare module '#mui/material/IconButton' {
interface IconButtonPropsOverrides {
square: boolean;
}
}
By taking a look into IconButton.d.ts from MUI,
export interface IconButtonPropsColorOverrides {}
export interface IconButtonPropsSizeOverrides {}
There is no override for props available...
My question is if there is any possibility of adding this override without having to alter the MUI code base. Is there a general approach over this?
Thanks

Typescript validation issue when using Storybook with MUI

I have an Typescript validation issue when trying to pass args as children to a MUI button documented in Storybook :-(
Any ideas how I can pass this as children and not get a Typescript error? I assume it's unhappy as its not a ReactNode. TIA
The default Mui Button doesn't have a label property.
https://mui.com/material-ui/api/button/
You need to extend the Mui Button Types. You can do something like:
import {
ButtonProps as MuiButtonProps,
} from '#mui/material/Button';
interface ButtonPropsOverrides {
label?: string
}
export type ButtonProps = MuiButtonProps & ButtonPropsOverrides;
Or do it like intended by Mui:
export default {
title: 'Atoms',
component: Button,
args: {
children: 'Label',
},
} as ComponentMeta<Shape>;
const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;
Because the Mui Button is using children as the actual label.

How to add custom close icon for Alert component in Material UI, globally

We can globally customise the severity icons shown in the Alert component through the theme:
MuiAlert: {
defaultProps: {
iconMapping: {
info: <Info/>,
success: <Success/>,
warning: <Warning>,
error: <Error/>,
}
},
styleOverrides: {
...
}
But, is there a way to do the same for the close icon, the one displayed when the OnClose prop is defined?
/**
* Callback fired when the component requests to be closed.
* When provided and no `action` prop is set, a close icon button is displayed that triggers the callback when clicked.
* #param {React.SyntheticEvent} event The event source of the callback.
*/
onClose?: (event: React.SyntheticEvent) => void;
I know the alternative solution is to create my own styled component derived from the Alert component and set the my custom close icon, but would like to avoid that and see if it is possible through the theme components customisation.
Otherwise I think it would be a nice to have and maybe should open a request for that.
Yes this is possible! Looking at the MUI Alert api docs there is a prop components where you can define the icon you want to display for the close icon, same with the button.
Then when you create your custom theme you can simply add that components prop to the defaultProps.
Note that we're not passing the actual <HomeIcon /> but just the function.
import { createTheme, ThemeProvider } from "#mui/material/styles";
import Alert from "#mui/material/Alert";
import HomeIcon from "#mui/icons-material/Home";
const theme = createTheme({
components: {
// Name of the component
MuiAlert: {
defaultProps: {
// The default props to change
components: {
CloseIcon: HomeIcon,
},
},
},
},
});
export default function DefaultProps() {
return (
<ThemeProvider theme={theme}>
<Alert onClose={() => {}}>Your alert with custom icon :)</Alert>
</ThemeProvider>
);
}

Overwrite Material UI prop types

I'm using React, Typescript, and Material UI. I'm using createMuiTheme.props to disable ripple for the ListItem component globally like this:
createMuiTheme({
props: {
MuiListItem: {
disableRipple: true,
disableTouchRipple: true,
disableFocusRibble: true
}
}
})
But Typescript complains that:
Types of property 'MuiListItem' are incompatible.
Type '{ disableRipple: boolean; disableTouchRipple: boolean; disableFocusRibble: boolean; }' has no properties in common with type 'Partial<OverrideProps<ListItemTypeMap<{}, "li">, "li">>'.ts(2322)
So I created a types.d.ts file in the project root and added the following code:
import { ListItemProps as PrevListItemProps } from "#material-ui/core/ListItem";
import { ButtonBaseProps } from "#material-ui/core/ButtonBase";
declare module "#material-ui/core/ListItem" {
export type ListItemProps = PrevListItemProps & ButtonBaseProps;
}
Which fixes the previous issue but now Typescripts errors because:
ListItem.d.ts(50, 13): 'ListItemProps' was also declared here.
Any idea how to fix this? Or disable ripple for ListItem without passing it each time to components?
Thanks!
You can disable ripple effect globally for all buttons:
https://material-ui.com/ru/getting-started/faq/
import { createMuiTheme } from '#material-ui/core'
const theme = createMuiTheme({
props: {
MuiButtonBase: {
disableRipple: true,
},
},
})
Or you can create your own component:
import ListItem from '#material-ui/core/ListItem'
const CustomListItem = props => <ListItem {...props} button disableRipple disableTouchRipple />

Fluent UI React commandbar usestate not possible

I am a newbee on Fluent UI React components. I am trying to implement React Router on the commandbar control from fluent UI react found here it is CommandBar with overflowing menu items. If I want to navigate to a different page with the menu items I use the history.push("/myLink") as explained here. But in order to get that working I would need to have access to useState in the functional component. the code looks like this:
export const CommandBarBasicExample: React.FunctionComponent = () => {
const [refreshPage, setRefreshPage] = useState(false);
return (
<div>
<CommandBar
items={_items}
overflowItems={_overflowItems}
overflowButtonProps={overflowProps}
farItems={_farItems}
ariaLabel="Use left and right arrow keys to navigate between commands"
/>
</div>
);
};
const _items: ICommandBarItemProps[] = [
{
key: 'newItem',
text: 'New',
cacheKey: 'myCacheKey', // changing this key will invalidate this item's cache
iconProps: { iconName: 'Add' },
subMenuProps: {
items: [
{ //first item in the menu
key: "AddProperty",
text: "Properties",
iconProps: { iconName: "Add" },
["data-automation-id"]: "newProperty", // optional
onClick: ()=>{handleclick()
setRefreshPage(true);
};
{
key: 'calendarEvent',
text: 'Calendar event',
iconProps: { iconName: 'Calendar' },
},
],
},
},
The Problem I have is that if I use setRefreshPage(true) VS code complains that the state variable is not recognized. if I put the useState somewhere else React complaints of a illegal use of useState. How can I get useState to be usable in the const _items object??
any help would be greatly appreciated.
Here's what's working for me with the same command bar component.
You have to make sure your router is setup as HashRouter and the path properties of your <Route/> s are setup like /#properties through the href property of the button - and not through onClick.
We have the routes file describing the routes:
/* routes.js */
export const Routes = {
Properties: 'properties'
}
We have this file, describing the contents of the command bar.
/* commandBarItems.js */
import Routes from './routes'
// IMPORTANT - CHECK OUT THE HREF PROP
const PropertiesButton = { key: Routes.Properties, name: 'Properties', href: `#${Routes.Properties}` };
export const CommandBarItems = { menu: [PropertiesButton] }
We have the app.js where you setup the hash router and the command bar component.
/* app.js */
import React, { Component } from 'react';
import { HashRouter as Router, Route } from "react-router-dom";
import { Fabric, initializeIcons, CommandBar } from 'office-ui-fabric-react';
import { PropertiesComponent } from './whichever-file-or-module';
import Routes from './routes';
import CommandBarItems from './commandBarItems';
class App extends Component {
constructor() {
super();
initializeIcons();
...
}
render() {
return (
<div>
<Fabric>
<Router>
<React.Fragment>
<CommandBar items={CommandBarItems.menu} farItems={CommandBarItems.farItems}/>
<Route path={`/${Routes.Properties}`} component={PropertiesComponent} />
</React.Fragment>
</Router>
</Fabric>
</div>
);
}
}

Resources