I know it's a simple question, but I am new in the React and WP Gutenberg world.
I am trying to create a plugin settings page using Gutenberg Components.
The tabs working fine and i know how to render fields, but i can't figure out how to assign to each tab the fields i want.
Here is the sample code:
import { TabPanel } from '#wordpress/components';
const onSelect = ( tabName ) => {
console.log( 'Selecting tab', tabName );
};
const MyTabPanel = () => (
<TabPanel
className="my-tab-panel"
activeClass="active-tab"
onSelect={ onSelect }
tabs={ [
{
name: 'tab1',
title: 'Tab 1',
className: 'tab-one',
},
{
name: 'tab2',
title: 'Tab 2',
className: 'tab-two',
},
] }
>
{ ( tab ) => <p>{ tab.title }</p> }
</TabPanel>
);
The Developer guide for Tab Panel mentions that
tabs is an array of objects, which new properties can be added to. By adding a new property, eg content to each of your tab objects, additional components can be set to render for each tab. The simple example uses ( tab ) => <p>{ tab.title }</p> but this could also include your own custom/existing components defined in content, eg:
import { Button, TabPanel } from '#wordpress/components';
const onSelect = (tabName) => {
console.log('Selecting tab', tabName);
};
const MyTabPanel = () => (
<TabPanel
className="my-tab-panel"
activeClass="active-tab"
onSelect={onSelect}
tabs={[
{
name: 'tab1',
title: 'Tab 1',
className: 'tab-one',
content: <p>Some content goes here</p>
},
{
name: 'tab2',
title: 'Tab 2',
className: 'tab-two',
content: <MyCustomComponent />
},
]}
>
{({ title, content, className }) => <div className={className}><h3>{title}</h3>{content}</div>}
</TabPanel>
);
const MyCustomComponent = () => {
return <><p>Here is a button</p><Button variant="secondary">Click me!</Button></>;
}
The children function (given as anonymous function, above </TabPanel>) renders the tabviews by passing in the active tab object, which now includes the extra content for the tab. The output of the example above is:
Aside from the Developer Guide, the Gutenberg source code is a good place to look for other examples of how TabPanel component can be implemented.. Hope this helps you with adding fields to your tabs..
Related
First of all, I am new to React.
Im using "easyui for React" for my App (Tab Component in my case)
My goals:
When click on a button on the LeftAccordion, a correspoding tab will be added and focus automatically.
When click on a button on the LeftAccordion, focus on the corresponding tab (if already existed)
Problems:
The tabs wouldn't be focused automatically after added. It always focus on the first one (Home) even when the prop selectedTabIndex has been changed (App's state changed).
WHAT I DID:
Click on button [PRODUCT] -> Tab PRODUCT will be added (but not focused)
Click on button [PRODUCT] again -> Tab PRODUCT still not focused
Click on button [CUSTOMER] -> Tab CUSTOMER will be added (but not focused), it still focus on tab HOME.
Now is the thing that made me get mad:
Click on button [PRODUCT] one more time -> Tab PRODUCT will be focused.
Click on button [CUSTOMER] -> Tab CUSTOMER will be focused.
Click on button [INVOICE] -> tab CUSTOMER will be added (but not focused), it still focus on the last one we selected.
Click on any button else on the LeftAccordion, the tab will be focused correctly again.
I knew that the UI will be re-renderred after state changed, but in my case only the tabs would be added, but not get the focus.
Please help! Thank you in advance!
App.js
import React, {useState} from 'react'
import {LinkButton, TabPanel, Tabs} from 'rc-easyui'
import LeftAccordion from "../../components/LeftAccordion";
const App = ({children}) => {
const [data, setData] = useState({
tabs: [{
title: 'HOME', iconCls: 'icon-store-1616', closable: false
}],
selectedTabIndex: 0
}
)
const handleAddTab = (newTab) => {
let clonedTabs = [...data.tabs] //// hoặc dùng data.tabs.slice() => React mới xác nhận có thay đổi giá trị của state
let currentTabs = []
let selectedTabIndex = 0
clonedTabs.forEach(tab => {
currentTabs.push(tab)
})
// check if tab is already exist by Tab's title
let existTab = currentTabs.find(x=>x.title === newTab.title)
if (existTab === undefined) {
currentTabs.push(newTab)
selectedTabIndex = currentTabs.length - 1 // select last tab
}else{
selectedTabIndex = currentTabs.indexOf(existTab)
}
setData(prev => ({
tabs: [...currentTabs],
selectedTabIndex: selectedTabIndex
})
)
}
return(
<>
<LeftAccordion addTabCallback={handleAddTab}></LeftAccordion>
<ContentArea>
<Tabs style={{height:'100%'}} scrollable selectedIndex={data.selectedTabIndex}
onTabClose={()=>{
console.log('close')
}
}
onTabSelect={() => {
console.log('OK')
}}
>
{
data.tabs.map((tab, index) => {
return (
<TabPanel key={index} title={tab.title} closable={tab.closable ? true: false} iconCls={tab.iconCls}>
<p>Tab {index}</p>
</TabPanel>
)
})
}
</Tabs>
</ContentArea>
</>
)
}
export default App
LeftAcordion.js
import React, {Component} from 'react';
import {LinkButton} from 'rc-easyui'
class LeftAccordion extends Component {
constructor(props) {
super(props);
this.state = {
buttons: [
{
text: 'HOME',
iconCls: 'icon-store-1616'
},
{
text: 'PRODUCT',
iconCls: 'icon-product-1616'
},
{
text: 'CUSTOMER',
iconCls: 'icon-customer-1616'
},
{
text: 'INVOICE',
iconCls: 'icon-invoice-1616'
}
]
}
}
render() {
return (
<div className={'leftaccordion-wrapper'}>
<div className={'accordion-splitter-horizontal'}></div>
{
this.state.buttons.map((button, index) => {
return(
<div key={index} className={'accordion-header panel-header f-noshrink'}>
<LinkButton iconCls={[button.iconCls]} plain
onClick={() => {
let tab = {title: button.text, iconCls: button.iconCls, closable: true}
this.props.addTabCallback(tab)
}
}>{button.text}</LinkButton>
</div>
)
})
}
</div>
);
}
}
export default LeftAccordion;
im making a portfolio website and have multiple different buttons with skills which contain an img and p tag. I want to show the description of each tag everytime a user clicks on the button. how can I do this? right now everytime user clicks it, all buttons show description.
const Skills = () => {
const [state, setState] = useState(false)
let skills = [
{ id: 1, desc: 'HTML5', state: false, img: htmlIcon },
{ id: 2, desc: 'CSS3', state: false, img: cssIcon },
{ etc....}
const showDesc = (id) => {
console.log(skills[id-1] = !state);
setState(!state)
}
return (
{skills.map(skill => (
<button onClick={(id) => showDesc(skill.id)}>
<img style={ state ? {display:'none'} : {display:'block'}} src={skill.img} />
<p style={ state ? {display:'block'} : {display:'none'}}>{skill.desc}</p>
</button>
))}
I recommend to manipulate element state instead of entry list. But if you really need to manipulate entry list you should add that list to your state. Then when you want to show/hide specific item, you need to find that item in state and correctly update entry list by making a copy of that list (with updated item). For example you can do it like this:
import React, { useState } from 'react';
const Skills = () => {
const [skills, setSkills] = useState([
{
id: 1,
desc: 'HTML5',
state: false,
img: htmlIcon, // your icon
},
{
id: 2,
desc: 'CSS3',
state: false,
img: cssIcon, // your icon
},
]);
const showDesc = (id) => {
const newSkills = skills.map((item) => {
if (item.id === id) {
return {
...item,
state: !item.state,
};
}
return item;
});
setSkills(newSkills);
};
return (
<div>
{skills.map(({
id,
img,
state,
desc,
}) => (
<button type="button" key={id} onClick={() => showDesc(id)}>
<img alt="img" style={state ? { display: 'none' } : { display: 'block' }} src={img} />
<p style={state ? { display: 'block' } : { display: 'none' }}>{desc}</p>
</button>
))}
</div>
);
};
Instead of manipulating all list, you can try to move show/hide visibility to list item itself. Create separate component for item and separate component for rendering that items. It will help you to simplify logic and make individual component responsible for it visibility.
About list rendering you can read more here
For example you can try something like this as alternative:
import React, { useState } from 'react';
const skills = [
{
id: 1,
desc: 'HTML5',
img: htmlIcon, // your icon
},
{
id: 2,
desc: 'CSS3',
img: cssIcon, // your icon
},
];
const SkillItem = ({
img,
desc = '',
}) => {
const [visibility, setVisibility] = useState(false);
const toggleVisibility = () => {
setVisibility(!visibility);
};
const content = visibility
? <p>{desc}</p>
: <img alt="img" src={img} />;
return (
<div>
<button type="button" onClick={toggleVisibility}>
{content}
</button>
</div>
);
};
const SkillList = () => skills.map(({
id,
img,
desc,
}) => <SkillItem img={img} desc={desc} key={id} />);
I created a CodeSandbox so I can elaborate my question.
I would like to ask for your suggestion on my Project:
I currently have a website portfolio app that are divided into 4 pages:
Loading.js directly fetch -> Home.js
About.js
Contact.js
Work.js – it displays a link of my projects that will open a Sliding Sidebar/Side Drawer
feature.
What I wanted to do is to fetch the individual project components and pass it in the Sliding Sidebar once a specific project was clicked by the user.
My question is what is the best way to manage the state? how do I pass the props from the project that was clicked and display the specific project component from the components folder?
CodeSandbox Link <----
updated work.js
import React, { useState } from "react";
import StyledWorkNav from "./StyledWorkNav";
import SideDrawer, { StyledDrawer } from "./SideDrawer";
import Project1 from "./components/Project1";
import Project2 from "./components/Project2";
import Project3 from "./components/Project3";
const Work = () => {
const [drawerOpen, setDrawerOpen] = useState(false);
const [projects, setProjects] = useState([
{ name: 'Project 1', projId: '1', dataText: 'Proj 1', comp:"" },
{ name: 'Project 2', projId: '2', dataText: 'Proj 2', comp:"Project2" },
{ name: 'Project 3', projId: '3', dataText: 'Proj 3', comp:"Project3" },
]);
const [selectedProject, setSelectedProject] = useState(null);
const strToComponent = {
Project1: <Project1/>,
Project2: <Project2/>,
Project3: <Project3/>
}
const openDrawerHandler = () => {
if (!drawerOpen) {
setDrawerOpen(true);
} else {
setDrawerOpen(false);
}
};
const closeDrawerHandler = () => {
setDrawerOpen(false);
};
// -------------------****** update **************
let drawer;
if (drawerOpen) {
drawer = (
<SideDrawer
close={closeDrawerHandler}
sidebar={{ StyledDrawer }}
// pass down here one of the wanted component : project1.js, 2 etc..
project={
<Project1
selectedProject={selectedProject} // you can pass the selected
// project as prop for
// project1.js for example
/>
}
/>
);
}
return (
<StyledWorkNav>
<ul>
{projects.map((project) => (
<li
key={project.projId}
onClick={() => {
setSelectedProject(project);
openDrawerHandler();
}}>
<p data-text={project.dataText}>{project.name}</p>
</li>
))}
{selectedProject && drawer}
</ul>
</StyledWorkNav>
);
};
export default Work;
you can do something like this :
Upadate
imports ......
// this state will contain all your projects
const [projects, setProjects] = useState([
{
id: 1,
name: "project1"
},
{
id: 2,
name: "project2"
},
.....
])
// this will contain on of the project selected from the list of
// projects
const [selectedProject, setSelectedProject] = useState({
id: 1,
name: "project1"
})
return (
<>
<List>
{ projects.map(project => (
<ListItem key={project.id} onClick={() => setSelectedProject(project)}>
{project.name}
</ListItem>
))
}
</List>
{
selectedProject &&
<Sidebar // the selected project goes here and change every time a different project selected
project={selectedProject}
/>
}
</>
)
export ......
I'm using antd's table component. I want to be able to filter the nodes down to any descendant.
I'm following the example of antd's custom filter.
The example is not a tree structure, so here's a codesandbox with the right setup.
onFilter seems to only loop over the root nodes. I don't know how to display Tesla > Model 3 it I type '3' in the search input. I know I have to return true for Tesla, but I still don't understand how to then also return true for Model 3
data
const data = [
{
key: "13",
name: "Tesla",
data: {
description: "Car manufacturer",
type: "Organization"
},
children: [
{
key: "4444",
name: "Model S",
data: {
description: "The fastest",
type: "Project"
}
},
{
key: "1444323",
name: "Model 3",
data: {
description: "The cheapest",
type: "Project"
}
}
]
},
{
key: "1",
name: "Microsoft",
data: {
description: "#1 software company",
type: "Organization"
}
}
];
App
class App extends React.Component {
state = {
searchText: '',
};
getColumnSearchProps = (dataIndex) => ({
filterDropdown: ({
setSelectedKeys, selectedKeys, confirm, clearFilters,
}) => (
<div style={{ padding: 8 }}>
<Input
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => this.handleSearch(selectedKeys, confirm)}
/>
<Button
type="primary"
onClick={() => this.handleSearch(selectedKeys, confirm)}
icon="search"
>
Search
</Button>
</div>
),
// onFilter seems to only loop over root nodes
onFilter: (value, record) => record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
render: (text) => (
<Highlighter
...
/>
),
})
render() {
const columns = [{
title: 'Name',
dataIndex: 'name',
key: 'name',
...this.getColumnSearchProps('name'),
}];
return <Table columns={columns} dataSource={data} />;
}
}
You can write a function to get a node's descendant values (based on dataIndex) and filter for any of those having the search text too.
This would go inside of getColumnSearchProps before the return value:
const getDescendantValues = (record) => {
const values = [];
(function recurse(record) {
values.push(record[dataIndex].toString().toLowerCase());
record.children.forEach(recurse);
})(record);
return values;
}
...and this would be the updated onFilter:
onFilter: (value, record) => {
const recordName = record[dataIndex] || record.data[dataIndex];
const searchLower = value.toLowerCase();
return recordName
.toString()
.toLowerCase()
.includes(searchLower)
||
getDescendantValues(record).some(descValue => descValue.includes(searchLower));
}
I've forked your sandbox here: https://codesandbox.io/s/10jp9wql1j
...it includes all the root nodes with any descendant that satisfies your search condition. However it does not filter out the other descendant nodes that do not satisfy your search condition, unfortunately.
After some searching, it seems like there isn't a great way to do this.
I'm using mobx and React, but the basic principles can be applied elsewhere. The idea is to give the rows you want to filter out a CSS class name to hide them.
I used mobx to create an observable of the checked filter values. You could also use React.useState. Using your example, let's say the observable is called ModelFilters. Then, on the table component, add rowClassName:
<Table
...
rowClassName={(record, index) => {
if (ModelFilters.includes(record.model) ) {
return ''
}
return 'some-css-class-with-display-none'
}}
/>
Any record that doesn't have a model that matches the selected filters will be hidden. Of course, if you are using the table to manipulate/submit data, you will have to handle that elsewhere. This is just for displaying purposes.
In my React component I have an array which will render linked social icons:
icons: { [
{ iconName: 'twitter', url: '#'},
{ iconName: 'facebook', url: '#'},
{ iconName: 'instagram', url: '#'},
] },
In render I'm showing all the social icon links from the array:
icons.map( ( icon, index ) => (
<a key={ index }
href='#'
onClick={() => {
setAttributes({ iconClicked: index });
}}
>
<Icon icon={ icon.iconName } /> /* this just renders an svg icon */
</a>
) )
When I click on a social icon, I want to be able to populate a form with the icon name and url and be able to change the values.
To do that, I'm first setting itemClicked to the index of the icon I clicked on (using onClick seen above).
Note: itemClicked as well as the icons array are state variables which update on change.
Next I'm trying to display the icon name in a select list and the url in a text input:
<SelectControl
label="Icon Name"
value={ icons[iconClicked].iconName }
options={ [
{ label: __( 'Twitter' ), value: 'twitter' },
{ label: __( 'Facebook' ), value: 'facebook' },
{ label: __( 'Instagram' ), value: 'instagram' },
{ label: __( 'YouTube' ), value: 'youtube' },
] }
onChange={( value ) => {
const newIcon = icons[iconClicked].iconName;
setAttributes({ newIcon: value });
}}
/>
<TextControl
label={ __( 'Enter URL' ) }
value={ icons[iconClicked].url }
onChange={( value ) => {
const newUrl = icons[iconClicked].url;
setAttributes({ newUrl: value });
}}
/>
When I click any icon, the select and text control populate with the correct values but they do not update when I try to change the value. I do not get errors, but the initial values don't change when onChange is triggered.
How can I make this work?