Show all variations of a component on a single page in React Storybook but still have Knobs? - reactjs

I have a button component that can be of different types eg primary, secondary, etc:
export const buttonTypes = [
'primary',
'secondary',
'tertiary',
'positive',
'negative',
]
const Button = ({ text, type }) => {
return(
<button className={type}>{text}</button>
)
}
Button.propTypes = {
text: PropTypes.string,
type: PropTypes.oneOf(buttonTypes),
}
In my Storybook file I'm mapping through the options. This means you can see all the variants on a single page and if another string was added to the buttonTypes array it would automatically be added to the style guide:
import ButtonComponent, { buttonTypes } from './Button';
const Button = () => {
return(
<div>
{
buttonTypes.map(type=>(
<ButtonComponent key={type} text={type} type={type} />
))
}
</div>
)
}
export default {
title: 'Components',
component: Button,
};
The problem is that this doens't work with with many of the add-ons eg knobs. For knobs to work you need Button to be the actual component, not a wrapper as I did above.
import ButtonComponent, { buttonTypes } from './Button';
const Button = () => {
return (
<ButtonComponent
type={select('type', buttonTypes, buttonTypes.primary)}
text="Button"
/>
);
};
Is there a way to use knobs and show all the variations on a single page? Ideally without having to create each component manually as this is more work and then won't automatically update if a new string is added to buttonTypes.

Use the grouping feature of knobs, that way each instance of your component will get its own knob instance instead of all the knob instances being shared between all of the component instances. You can even mix grouped knobs with non-grouped nobs if you want certain things to be shared and other not to be.
In the following example, I have a <Button/> story where each instance has its own copy of the type and disabled properties, but the text is shared between them all.
Each button type gets its own panel where you can set its type and disabled. The "Other" group contains any knobs that didn't have their group set (such as text).
src/Button/Button.component.jsx
import * as React from "react";
import "./Button.style.css";
export const Button = ({
text,
type,
disabled,
onClick
}) => (
<button
className={`button button--${type} ${disabled ? "button--disabled" : ""}`}
disabled={disabled}
onClick={onClick}
children={text}
/>
);
src/Button/Button.stories.jsx
import * as React from "react";
import {withKnobs, text, boolean, select} from "#storybook/addon-knobs";
import {action} from "#storybook/addon-actions";
import {Button, buttonTypes} from "./";
export default {
title: "Button",
component: Button,
decorators: [withKnobs]
};
export const ButtonStory = () => {
const buttontext = text("Text", "Click Me");
return (
<div>
{buttonTypes.map(buttonType => (
<div key={buttonType}>
<Button
type={select("Type", buttonTypes, buttonType, buttonType)}
disabled={boolean("Disabled", false, buttonType)}
onClick={action(`${buttonType} clicked`)}
text={buttontext}
/>
</div>
))}
</div>
);
};
ButtonStory.story = {
name: "All"
}
src/Button/Button.types.js
export const buttonTypes = [
"primary",
"secondary",
"tertiary"
];
src/Button/Button.style.css
.button {
padding: 0.5em;
font-size: 1.25em;
border-radius: 10px;
border-width: 2px;
border-style: solid;
border-color: black;
}
.button--primary {
background-color: rgb(132, 198, 106);
color: black;
border-color: black;
}
.button--secondary {
background-color: rgb(194, 194, 194);
color: black;
border-color: black;
}
.button--tertiary {
background-color: transparent;
color: inherit;
border-color: transparent;
}
.button--disabled {
background-color: rgb(194, 194, 194);
color: rgb(105, 102, 102);
border-color: rgb(105, 102, 102);
}
src/Button/index.js
export {Button} from "./Button.component";
export {buttonTypes} from "./Button.types";

Related

How to reference a styled component that is in a different dom

I like to add styling to a styled component that is in a different dom.
Like this Material-ui Menu component example, the dashboard button is highlighted gray, and the menu drop is highlighted light blue.
They are written in the same component file but they are in different dom.
I like to add styling to the Menu component from Button component.
Is that possible?
Demo sandbox: https://codesandbox.io/s/si8tr5?file=/demo.js
Menu material ui official doc: https://mui.com/material-ui/react-menu/#basic-menu
index.jsx
import * as React from 'react';
import MenuItem from '#mui/material/MenuItem';
import {DisplayMenu, DisplayButton} from './styles'
export default function BasicMenu() {
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<div>
<DisplayButton
id="basic-button"
aria-controls={open ? 'basic-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
>
Dashboard
</DisplayButton>
<DisplayMenu
id="basic-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
'aria-labelledby': 'basic-button',
}}
>
<MenuItem onClick={handleClose}>Profile</MenuItem>
<MenuItem onClick={handleClose}>My account</MenuItem>
<MenuItem onClick={handleClose}>Logout</MenuItem>
</DisplayMenu>
</div>
);
}
styles.js
import styled from 'styled-components';
import Button from '#mui/material/Button';
import Menu from '#mui/material/Menu';
export const DisplayMenu = styled(Menu)`
padding: 20px;
`;
DisplayMenu.displayName = 'DisplayMenu';
export const DisplayButton = styled(Button)`
margin: 10px;
${DisplayMenu}{
& .MuiPaper-root {
width: 300px;
}
}
`;
DisplayButton.displayName = 'DisplayButton';
I know just doing this below will work but this is just an example and the reality is more complicated.
I just made it simple to ask this question here.
export const DisplayMenu = styled(Menu)`
padding: 20px;
& .MuiPaper-root {
width: 300px;
}
`;
Your current code won't work because you are applying styles to descendent DisplayMenu components (DisplayButton is a sibling). I don't really think it makes sense to do this but you could select the sibling:
export const DisplayButton = styled(Button)`
margin: 10px;
& + ${DisplayMenu}{
& .MuiPaper-root {
width: 300px;
}
}
`;
I think styling DisplayMenu directly makes the most sense (your last approach).
However If this is a permutation of DisplayMenu when used with a button, I think you should consider making the wrapper div into a styled component since that allows you to apply contextual styles to the menu (change its style based on 'where' it was used):
const MenuButton = styled.div`
${DisplayMenu}{
& .MuiPaper-root {
width: 300px;
}
`
// And use this in your component
<MenuButton>
<DisplayButton {...} />
<DisplayMenu {...} />
</MenuButton>
This way we only add the width to DisplayMenu when used within the context of MenuButton

ReactJS clicking button

I am hoping that you can help me with this task, I just want to create a Like button, the initial counter for the button is 100, if the button is clicked it will add 1 and it will highlight (you can use the classname tool) and if the user undo their like it will decrease by one and it will not highlight
import cx from 'classnames';
import { Component } from 'react';
export default class LikeButton extends Component {
state = {
active: false,
count: 100
}
formatCount() {
const {count} = this.state.count
return count === 100 ? 100 : 101
}
render() {
return (
<>
<div>
<button className={this.state.active === false ? 'like' : 'like-button'}>Like | {this.formatCount()}</button>
</div>
<style>{`
.like-button {
font-size: 1rem;
padding: 5px 10px;
color: #585858;
}
.liked {
font-weight: bold;
color: #1565c0;
}
`}</style>
</>
);
}
}
Here's how to do it using functional components in React (which I'd strongly recommend):
import "./like.css"
export default function Like(){
const [count, setCount] = useState(100);
const [active, setActive] = useState(false);
function handleClick(){
if(active) {
// handle unliking
setActive(false);
setCount(oldCount => oldCount - 1);
} else {
// handle liking
setActive(true);
setCount(oldCount => oldCount + 1);
}
}
return(
<div>
<button
className={active ? 'liked' : 'unliked'}
onClick={handleClick}
>
Like | {count}
</button>
</div>
)
}
and like.css contents:
.unliked {
font-size: 1rem;
padding: 5px 10px;
color: #585858;
}
.liked {
font-weight: bold;
color: #1565c0;
}
You can rework this to the class-based approach pretty easily (though I'd recommend using functional components, they're really cool). Basically, you keep track of the count and whether or not they've already pressed the like button, and conditionally handle onClick based on the current state.

How to change button background color when I click the button in React using Hooks

I am working on a React project, In my project I have two buttons, for First button I assigned a state for second button I written a function and I assigned a state as well. but my onClick function is not working. please help me to resolve this isssue.
This is App.js
import React, { useState } from "react";
import { Button } from "antd"
import 'antd/dist/antd.css';
import "./App.css";
const App = () => {
const [buttonOne, setButtonOne] = useState("red")
const [buttonTwo, setButtonTwo] = useState("blue")
const buttonTwoBackgroundColor = () => {
setButtonTwo({
backgroundColor: "red",
border: "red"
})
}
return (
<div>
<Button style={{backgroundColor: buttonOne, border: buttonOne}} className="one" type="primary">First</Button>
<Button style={{backgroundColor: buttonTwo, border: buttonTwo}} onClick={buttonTwoBackgroundColor} className="two" type="primary">Second</Button>
</div>
)
}
export default App
This is App.css
.one {
margin-right: 5px;
margin-left: 250px;
margin-top: 50px;
}
.two, .three, .four, .five {
margin-right: 5px;
}
In the function buttonTwoBackgroundColor you set setButtonTwo to a object. it should be a string.
const buttonTwoBackgroundColor = () => {
setButtonTwo("red")
}
change
onClick={buttonTwoBackgroundColor}
to
onClick={setButtonTwo("red")}
you are using useState as string at initialization and assigning an object in function below, which is not proper way!
You are trying to set state to be an object when you have defined the defualt state as a string.
Update the default state to an object and then access properties like this:
import React, { useState } from "react";
import { Button } from "antd";
import "./styles.css";
export default function App() {
const [buttonOne, setButtonOne] = useState("red");
const [buttonTwo, setButtonTwo] = useState({backgroundColor: "blue", border: "blue"});
const buttonTwoBackgroundColor = () => {
setButtonTwo(prevState => ({
...prevState,
backgroundColor: 'blue',
border: 'red'
}));
};
return (
<div>
<Button
style={{ backgroundColor: buttonOne, border: buttonOne }}
className="one"
type="primary"
>
First
</Button>
<Button
style={{ backgroundColor: buttonTwo.backgroundColor, border: buttonTwo.border }}
onClick={buttonTwoBackgroundColor}
className="two"
type="primary"
>
Second
</Button>
</div>
);
}

How to trigger a cancel event on inline edit view of atlaskit?

I'm trying to programmatically trigger a onCancel event on Inline edit view component of Atlaskit, But I couldn't find any API docs where I can trigger on cancel event on this inline component.
import React from 'react';
import styled from 'styled-components';
import Textfield from '#atlaskit/textfield';
import { gridSize, fontSize } from '#atlaskit/theme';
import InlineEdit from '#atlaskit/inline-edit';
const ReadViewContainer = styled.div`
display: flex;
font-size: ${fontSize()}px;
line-height: ${(gridSize() * 2.5) / fontSize()};
max-width: 100%;
min-height: ${(gridSize() * 2.5) / fontSize()}em;
padding: ${gridSize()}px ${gridSize() - 2}px;
word-break: break-word;
`;
interface State {
editValue: string;
}
export default class InlineEditExample extends React.Component<void, State> {
state = {
editValue: 'Field value',
};
render() {
return (
<div
style={{
padding: `${gridSize()}px ${gridSize()}px ${gridSize() * 6}px`,
}}
>
<InlineEdit
defaultValue={this.state.editValue}
label="Inline edit"
editView={fieldProps => <Textfield {...fieldProps} autoFocus />}
readView={() => (
<ReadViewContainer>
{this.state.editValue || 'Click to enter value'}
</ReadViewContainer>
)}
onConfirm={value => this.setState({ editValue: value })}
/>
</div>
);
}
}
In my opinion the best way to do this is to use the InlineEditUncontrolled component and make it a controlled component using the following props: onConfirm, onCancel, isEditing. After that you can change internal isEditing state to false.

How to render react-icons on native?

I want to render icons on native from the react-icons package, but the icon is not showing. Currently my code is looking like this:
import React from 'react';
import { View, Text } from 'react-native';
import PropTypes from 'prop-types';
import styled from 'styled-components/native';
import * as MaterialDesign from 'react-icons/md';
const StyledTab = styled.View`
display: flex;
align-items: center;
justify-content: center;
`;
const Tab = ({ icon, type, text, color }) => {
let mdIcon = null;
if (icon) {
mdIcon = React.createElement(Text, [], [MaterialDesign[icon]]);
}
return (
<StyledTab icon={icon} type={type} text={text} color={color}>
{mdIcon}
<Text>{text}</Text>
</StyledTab>
);
};
Tab.propTypes = {
/** Text of tab */
text: PropTypes.string,
/** Text of tab */
type: PropTypes.oneOf(['default', 'inline', 'icons']),
color: PropTypes.string,
icon: PropTypes.string
};
Tab.defaultProps = {
text: null,
type: 'default',
color: '#000',
icon: null
};
/**
* #component
*/
export default Tab;
So the part:
const Tab = ({ icon, type, text, color }) => {
let mdIcon = null;
if (icon) {
mdIcon = React.createElement(Text, [], [MaterialDesign[icon]]);
}
return (
<StyledTab icon={icon} type={type} text={text} color={color}>
{mdIcon}
<Text>{text}</Text>
</StyledTab>
);
};
is not rendering the icon and not showing anything. Can someone help me with this? Do i need a different way to render this icon on native?

Resources