How to pass props into a Stencil child component in Storybook? - storybook

I have two Stencil components. MyCard and MyButton.
MyButton.tsx
import { Component, h, Prop } from '#stencil/core';
#Component({
tag: 'my-button',
styleUrl: 'my-button.css',
shadow: true,
})
export class MyButton {
#Prop() label: string;
render() {
return (
<div>
<button>
{this.label}
</button>
</div>
);
}
}
MyButton.stories.ts
const Template = (args) => `<my-button label="${args.label}"></my-button>`;
export const Example = Template.bind({});
Example.args = {
label: 'my custom button'
}
MyCard.tsx
import { Component, h, Prop } from '#stencil/core';
#Component({
tag: 'my-card',
styleUrls: ['my-card.scss'],
shadow: true,
})
export class MyCard {
render() {
return (
<div>
<div>
<my-button></my-button>
</div>
</div>
)
}
}
MyCard.stories.ts
const MyCardDefault = (args) => (
`<my-card>
<my-button label="${args.label}"></my-button>
</my-card>`
)
export const CardExample = MyCardDefault.bind({});
CardExample.args = {
label: "CardButton"
}
If I open the MyButton component in Storybook, the args are properly passed, and the label is applied to the button, but when I open the MyCard component, the button innerHTML is empty. The props are not passed. Why? How do I pass props to a nested child component in Stencil?

I don't know Storybook, but your MyCard component does not have any properties and does not set any properties on it's my-button element, so binding a label to the card won't do anything because MyCard doesn't use the label. I think you need:
export class MyCard {
#Prop() label: string;
render() {
return (
<div>
<div>
<my-button label={this.label}></my-button>
</div>
</div>
)
}
}

Related

Why is class at DOM not same with className at component

I pass className props to the child component, but sometimes in the production stage className for link active is not actually write to the DOM element, like this:
Child component (Link) (catch props className active link from parent)
Child component (Link) in DOM (client does not write class if link active)
My Code like this
Navbar
// Navbar.tsx
import classnames from 'classnames';
import { useRouter } from 'next/router';
...
const Navbar = () => {
const router = useRouter();
const navigations = [
{
href: '/',
label: 'Home'
},
{
href: '/profile',
label: 'Profile'
}
];
return (
...
{navigations.map((nav) => (
<NavLink key={nav.label} href={nav.href} isActive={router.asPath === nav.href}>
{nav.label}
</NavLink>
))}
...
);
};
export default Navbar;
Parent component
// NavLink.tsx
import classnames from 'classnames';
import Link from '#/components/elements/Link';
import styles from '#/components/parts/Navbar/styles.module.scss';
import type { NavLinkProps } from './types';
const NavLink = ({ children, href, isActive }: NavLinkProps) => (
<Link
href={href}
className={classnames(styles.navbar__link, {
[styles.navbar__link_active]: isActive
})}
>
{children}
</Link>
);
export default NavLink;
Child component
// Link.tsx
import dynamic from 'next/dynamic';
import type { LinkProps } from './types';
const NextLink = dynamic(() => import('next/link'));
const Link = ({ children, href, target, isExternal, className, label, role }: LinkProps) => {
if (isExternal) {
return (
<a
href={href}
target={target ?? '_blank'}
rel="noopener noreferrer"
className={className}
aria-label={label}
role={role}
>
{children}
</a>
);
}
return (
<NextLink href={href}>
<a target={target ?? '_self'} className={className} aria-label={label} role={role}>
{children}
</a>
</NextLink>
);
};
export default Link;
Try using template literals:
<Link className={`${classnames(styles.navbar__link, {
[styles.navbar__link_active]: isActive
}}`}>

Story with its own states and methods

To demonstrate a component I try to write custom state and methods for a story.
For example, I have a component ListItems who accepts an array of string as Input.
In the story of this component I want to show an interactive example of the usage of this component.
So my story will have internal state "items" and internal method "addItem"
I know how to do that with React, but I'm stuck with Angular.
Here is a React way to do that:
(View in codesandbox)
// ListItems.tsx
import React from "react";
export type ListItemsProps = { items: string[] };
export const ListItems = ({ items = [] }: ListItemsProps) => {
return (
<ul>
{items.map((item, key) => (
<li key={key}>{item}</li>
))}
</ul>
);
};
// ListItems.stories.tsx
import React, { useState } from "react";
import { ListItems } from "./ListItems";
export default {
title: "ListItems",
component: ListItems
};
export const Text = () => {
const [items, setItems] = useState(["Demo Item"]);
const [value, setValue] = useState("");
const addItem = () => {
setItems([...items, value]);
setValue("");
};
return (
<div>
<ListItems items={items} />
<input value={value} onChange={(e) => setValue(e.target.value)} />
<input type="submit" onClick={addItem} value="add" />
</div>
);
};
};
How can I write the same story with following Angular Component ?
import { Component, OnInit, Input } from "#angular/core";
#Component({
selector: "app-list-items",
template: `<ul>
<li *ngFor="let item of items">{{ item }}</li>
</ul>`
})
export default class ListItems implements OnInit {
#Input() items: string[] = [];
constructor(){}
ngOnInit(): void {}
}
Finally I found a solution on Angular, but it's not an elegant one...
Maybe someone know a better way !
And I can't find a solution to show the code story template on "Show code" feature.
import { Story, Meta } from '#storybook/angular/types-6-0';
import { ListItemsComponent } from './list-items.component';
import { Component } from '#angular/core';
export default {
title: 'Demo/ListItems',
component: ListItemsComponent,
} as Meta;
const Template: Story<ListItemsComponent> = (args: ListItemsComponent) => ({
props:args,
});
export const BasicDemo = Template.bind({})
BasicDemo.args={
items: ["Basic Demo", "Without interaction"]
}
// Create a dedicated component for the interactive story
#Component({
selector: 'story-list-items',
template: `
<core-list-items [items]="items"></core-list-items>
<input type="text" [(ngModel)]="value" /><button (click)="addItem()">add</button>
`,
})
class InteractiveDemoComponent{
items = [];
value: string = '';
addItem(){
this.items = [...this.items, this.value];
this.value = ""
}
}
const InteractiveTemplate: Story<ListItemsComponent> = (args: ListItemsComponent) => ({
props:args,
component: InteractiveDemoComponent,
});
export const InteractiveDemo = InteractiveTemplate.bind({});
InteractiveDemo.args = {
items: ["Interactive Demo"]
}

How to apply user defined style(CSS) to React-Treebeard?

I'm working with treebeard in my react project.
when I try to add a user-defined style it's not getting applied to the container.
I have tried different methods to achieve this.
I tried to define the style from tag, tried to define style.css in a different file, import it and assign it to style in Treedeard tag.
the main goal is to change the background color and the position.
Here is my code:
workspace.jsx
import React from "react";
import {Treebeard} from 'react-treebeard';
import data from "./data";
export class WorkSpace extends React. Component {
constructor(props){
super(props);
this.state = {
data: {data}
};
this.onToggle = this.onToggle.bind(this);
const decorators = {
Container: (props) => {
return (
<div style={props.backgroundColor="yellow"}>
</div>
);
}
};
}
onToggle(node, toggled){
const {cursor, data} = this.state.data;
if (cursor) {
this.setState(() => ({cursor, active: false}));
}
node.active = true;
if (node.children) {
node.toggled = toggled;
}
this.setState(() => ({cursor: node, data: Object.assign({}, data)}));
}
render() {
return (
<div className="base_container">
<h3>Select Component</h3>
<div className="components">
<Treebeard
data={data}
onToggle={this.onToggle}
style = {style_components}
decorators={this.decorators}
/>
</div>
</div>
);
}
}

Access Event Of Component That Is Passed As Prop

I pass a React Component as a Prop to a child.
That component has an event.
In child component, I want to get access to that event and bind it to a method inside child. How can I do that ?
I often use Semantic-UI React Modal as following:
class Example extends Component {
constructor(props) {
super(props);
this.state = {
modalOpen: false
}
}
handleOpen = () => this.setState({ modalOpen: true })
handleClose = () => this.setState({ modalOpen: false })
render(){
return(
<Modal
onClose={this.handleClose}
open={this.state.modalOpen}
trigger={<Button onClick={this.handleOpen}>Gandalf</Button>}>
<Modal.Header>Balrog</Modal.Header>
<Modal.Content>
<h1>You shall not pass!</h1>
{/*
Some form that closes Modal on success
onSuccess : this.handleClose
*/}
<Button onClick={this.handleClose}>Grrrrr</Button>
</Modal.Content>
</Modal>
)
}
}
export default Example
Now I want to make it reusable
import React, { Component } from 'react'
import { Modal } from 'semantic-ui-react'
class ReusableModal extends Component {
constructor(props) {
super(props);
this.state = {
modalOpen: false
}
}
handleOpen = () => this.setState({ modalOpen: true })
handleClose = () => this.setState({ modalOpen: false })
render(){
return(
<Modal
onClose={() => this.handleClose}
open={this.state.modalOpen}
{/*
How to access onClick event on trigger prop content ?
this.prop.trigger can be a Button or a Menu.Item
*/}
{...this.props}>
{this.props.children}
</Modal>
)
}
}
How can I have access to the trigger prop Component and bind its onClick event to handleOpen method ?
Edit :
to be more precise here is what I'm looking for
<ChildComponent trigger={<Button>This button should fire a function defined in child component</Button>} />
ChildComponent extends Component {
functionToCall = () => { console.log('hello') }
// I would like to :
// let myButton = this.props.trigger
// myButton.onClick = functionToCall
}
In React, data flows from parent to child. If there are multiple child components that have events that need to trigger changes in parent component you must fire callback function in child component.
Parent component:
handleOpen = () => { // do something }
(...)
<childComponent onClickCallback={this.handleOpen}
And in child component:
<button onClick={this.props.onClickCallback}> Click to close</button>
Pass this.handleOpen as a prop and call it as a prop in child component, and it will trigger function in the parent component, where you can handle data however you want.
Is this what you asked for?
The key here is to clone element
ChildComponent extends Component {
functionToCall = () => { console.log('hello') }
this.Trigger = React.cloneElement(
this.props.trigger,
{ onClick: this.functionToCall }
)
}

How do I access css/scss with react?

I have a react component where I am trying to change the background color of the css when clicking the div.
I know you can set the color in the component, but I am using this component many times, and don't to make multiple component files with just a different color, and even if I did, I am curious besides the fact.
How can I access (or even console.log to figure it out on my own) the css file and its properties through the component? Thanks ahead of time.
If you want to keep all background-color styles in your .css/.scss file, you will need to have a good className strategy to link the styles to your components. Here is my suggestion:
styles.scss
.blue {
background-color: blue;
&.clicked {
background-color: red;
}
}
Container.js
import React from 'react';
import ClickableDiv from './ClickableDiv.js';
const Container = () => (
<ClickableDiv className="blue">
<p>This is my text.</p>
</ClickableDiv>
);
export default Container;
ClickableDiv.js
import React, { Component } from 'react';
class ClickableDiv extends Component {
constructor() {
super();
this.state = { clicked: false };
this.handleDivClick = this.handleDivClick.bind(this);
}
handleDivClick() {
this.setState({ clicked: true });
}
render() {
const divClassName = [this.props.classname];
if (this.state.clicked) divClassName.push('clicked');
return (
<div className={divClassName.join(' ').trim()} onClick={this.handleDivClick}>
{this.props.children}
</div>
);
}
}
export default ClickableDiv;
Rendered Markup
Unclicked:
<div class="blue"><p>This is my text.</p></div>
Clicked:
<div class="blue clicked"><p>This is my text.</p></div>
You can pass in the desired background color as a prop, and use internal state with an onClick handler.
Container.js
import React from 'react';
import ClickableDiv from './ClickableDiv';
const Container = () => (
<ClickableDiv backgroundColor="#FF0000">
<p>This is my text.</p>
</ClickableDiv>
);
export default Container;
ClickableDiv.js
import React, { Component } from 'react';
class ClickableDiv extends Component {
constructor() {
super();
this.state = {};
this.handleDivClick = this.handleDivClick.bind(this);
}
handleDivClick() {
const { backgroundColor } = this.props;
if (backgroundColor) this.setState({ backgroundColor });
}
render() {
const { backgroundColor } = this.state;
return (
<div style={{ backgroundColor }} onClick={this.handleDivClick}>
{this.props.children}
</div>
);
}
}
export default ClickableDiv;
Better to make an external css file and write your css code in that file and just import that one in index.html

Resources