I'm using TreeView, TreeItem of material ui.
I'm trying to style conditionally each node (selected, hover, focus).
actually what I'm trying to do is that only node that not disabledButton (props that come from outside) will be in right color.
only node that selectable will be in active color (expand node not means that node is selectable).
something like this example
I did this example, but the behavior is really weird, sometimes the "child-4" looks like the style of disabledButton even I sent the right props, maybe because it nested props, sometimes the other nested not get the right colors of the hover, focus and selected..
link for example (with colors)
example with different colors
I want it to be in real colors also with the theme (active should be like blue color, hover gray) so I created this example.
example with theme colors
sometimes it works as expected and sometimes is not.
I looked in this issue but it's not like my example but there it uses in classes but I don't want to send this way
After a little while this is what I got. I tried to match the design of the gif as good as possible.
I used this data which I derived from the question. This example has one disabled node and parent node (my-photo.jpg and zip directory). The data array can be expanded endlessly if it matches TreeData type.
export type TreeData = {
id: string;
name: string;
disabledButton: boolean;
children?: TreeData[];
};
const data: TreeData[] = [
{
id: "1",
name: "My Web Site",
disabledButton: false,
children: [
{
id: "2",
name: "images",
disabledButton: false,
children: [
{ id: "3", name: "logo.png", disabledButton: false },
{ id: "4", name: "body-back.png", disabledButton: false },
{ id: "5", name: "my-photo.jpg", disabledButton: true }
]
},
{
id: "6",
name: "resources",
disabledButton: false,
children: [
{
id: "7",
name: "pdf",
disabledButton: false,
children: [
{ id: "8", name: "brochure.pdf", disabledButton: false },
{ id: "9", name: "prices.pdf", disabledButton: false }
]
},
{
id: "10",
name: "zip",
disabledButton: true,
children: [{ id: "11", name: "test.zip", disabledButton: false }]
}
]
}
]
}
];
The CustomTreeView consists of CustomTreeItem that again makes use of CustomContent.
CustomContent is used to handle all events and to display another icon (folder) next to the expandIcon.
In the CustomTreeItem I set the width to fit-content to not select the whole row, but to match the example from the gif:
const CustomTreeItem = (props: TreeItemProps) => (
<TreeItem
ContentComponent={CustomContent}
{...props}
sx={{
width: "fit-content",
".MuiTreeItem-content": {
py: "2px",
width: "fit-content"
}
}}
/>
);
The interesting part is the styling of the CustomTreeView and its usage.
I have packed the classes into objects, which can be overwritten easily. The theming happens in the styles class which is in styles/Styles.ts.
// Styles.ts
import { createTheme } from "#mui/material";
export const getMuiTheme = () =>
createTheme({
palette: {
primary: {
main: "#2160dd"
},
secondary: {
main: "#d3d3d3"
}
}
});
// CustomTreeView.tsx
const classes = {
focused: {
bgcolor: "transparent",
py: "1px",
px: "7px",
border: `1px solid ${getMuiTheme().palette.secondary.main}`
},
selected: {
bgcolor: getMuiTheme().palette.primary.main,
color: "white"
}
};
const CustomTreeView = ({ data }: { data: TreeData[] }) => {
return (
<Box mt={2} ml={2} bgcolor="white" width="300px">
<ThemeProvider theme={getMuiTheme()}>
<TreeView
defaultCollapseIcon={<ArrowDropDownIcon />}
defaultExpandIcon={<ArrowRightIcon />}
defaultEndIcon={<InsertDriveFile />}
sx={{
".MuiTreeItem-root": {
".Mui-focused:not(.Mui-selected)": classes.focused,
".Mui-selected, .Mui-focused.Mui-selected, .Mui-selected:hover":
classes.selected
}
}}
>
{renderTreeData(data)}
</TreeView>
</ThemeProvider>
</Box>
);
};
To render arbitrarily nested data we make use of the recursive method renderTreeData. This way the data array can be expanded endlessly as long as it matches TreeData type.
export const renderTreeData = (data: TreeData[]) => {
return data.map((item) => (
<React.Fragment key={item.id}>
{item.children ? (
<CustomTreeItem
nodeId={item.id}
label={item.name}
disabled={item.disabledButton}
icon={<FolderOutlined />}
>
{renderTreeData(item.children)}
</CustomTreeItem>
) : (
<CustomTreeItem
nodeId={item.id}
label={item.name}
disabled={item.disabledButton}
icon={getFileIcon(item.name)}
/>
)}
</React.Fragment>
));
};
Live Demo
I am developing a personal website using typescript and react, and for my footer, I am mapping through an array of objects which each have a url property, as well as an icon property. The Icon property is supposed to have multiple different React Icons as the value, so that through each iteration, I can display a different Icon. When I originally implemented this in javascript, my code worked just fine, but after switching my codebase over to typescript, I am presented with this error:
"Parsing error: '>' expected.eslint
'FaGithub' refers to a value, but is being used as a type here. Did you mean 'typeof FaGithub'?ts(2749)"
This is the typescript implementation that I have so far. I have tried removing the angled brackets, and that eliminates the error, but I am unable to actually display the icon when mapping through the array. I would really appreciate any advice.
const socialIcons: { icon: IconType, url: string }[] = [
{
icon: <FaGithub />,
url: "./"
},
{
icon: <FaLinkedin />,
url: "./"
},
{
icon: <FaTwitter />,
url: "./"
},
{
icon: <FaInstagram />,
url: "./"
},
{
icon: <FaFacebook />,
url: "./"
},
]
{socialIcons.map((icons, index) => {
const {icon, url} = icons;
return (
<a href={url} key={index} target="_blank" rel="noopener noreferrer">
{icon}
</a>
);
})}
This is how the socialIcons array originally looked in regular javascript.
const socialIcons = [
{
icon: <FaGithub />,
url: "./"
},
{
icon: <FaLinkedin />,
url: "./"
},
{
icon: <FaTwitter />,
url: "./"
},
{
icon: <FaInstagram />,
url: "./"
},
{
icon: <FaFacebook />,
url: "./"
},
]
I figured out a solution, all I needed to do was change the file extension from .ts to .tsx, and make the type of the value a JSX.Element to input react components as values within objects without a typescript compiler error.
I had difficulties to type icons myself. I found this solution to be working.
export interface IRoute {
path: string;
name: string;
icon: ReactNode;
layout: string;
}
export const routes: IRoute[] = [
{
path: "/dashboard",
name: "Dashboard",
icon: <FiHome />,
layout: ""
},
{...}
]
I have tried to make a menu, so i create a menuList to config the menu with getMenuNodes(), but Ant framework has been upgraded from v3 to v4 which the icon method has been changed. they are now using icon={<PieChartOutlined />} to instead of icon='PieChartOutlined', everything is working well, the icon area shows the word <PieChartOutlined />right now. i do not know why it happened, please help me to solve this problem.
left-navigation.js
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import logo from '../../assets/images/logo.png';
import './index.less';
import { Menu } from 'antd';
import { PieChartOutlined } from '#ant-design/icons';
import menuList from '../../config/menuConfig';
const { SubMenu } = Menu;
export default class LeftNav extends Component {
getMenuNodes = menuList => {
return menuList.map(item => {
if (!item.children) {
return (
<Menu.Item key={item.key} icon={item.icon}>
<Link to={item.key}>{item.title}</Link>
</Menu.Item>
);
} else {
return (
<SubMenu key={item.key} icon={item.icon} title={item.title}>
{this.getMenuNodes(item.children)}
</SubMenu>
);
}
});
};
render() {
return (
<div className="left-nav">
<Link to="./" className="left-nav-header">
<img src={logo} alt="" />
<h1>Backend System</h1>
</Link>
<Menu
mode="inline"
theme="dark"
>
{this.getMenuNodes(menuList)}
</Menu>
</div>
);
}
}
menuList.js
const menuList = [
{
title: 'Home',
key: '/home',
icon: '<PieChartOutlined />',
},
{
title: 'Item',
key: '/products',
icon: '<PieChartOutlined />',
children: [
{
title: 'Category Control',
key: '/category',
icon: '<PieChartOutlined />',
},
{
title: 'Product Control',
key: '/product',
icon: '<PieChartOutlined />',
},
],
},
{
title: 'User Control',
key: '/user',
icon: '<PieChartOutlined />',
},
{
title: 'Role Control',
key: '/role',
icon: '<PieChartOutlined />',
},
{
title: 'Diagram',
key: '/charts',
icon: '<PieChartOutlined />',
children: [
{
title: 'Bar',
key: '/charts/bar',
icon: '<PieChartOutlined />',
},
{
title: 'Line',
key: '/charts/line',
icon: '<PieChartOutlined />',
},
{
title: 'Pie',
key: '/charts/pie',
icon: '<PieChartOutlined />',
},
],
},
];
export default menuList;
You are passing a string of '<PieChartOutlined />', you need to pass the component directly.
import { PieChartOutlined } from '#ant-design/icons';
and:
{
title: 'Product Control',
key: '/product',
icon: <PieChartOutlined />,
},
You'll need to install ant-design/icons if you haven't already:
npm install --save #ant-design/icons
Since reason performance on the previous version, antd team was apply tree-shaking to use icon. More detail, you can check https://ant.design/docs/react/migration-v4
I want to show the sidebar content in a different background color.For that I've tried TouchableOpacity underlay.But that is not the one I'm looking for.After giving TouchableOpacity ,it will change the color of the text only not the entire list background.How do I change the listitem background color as I'm using native base ui kit.Please help.Is there any method to do that?This is how the sidebar looks like.I've done something likes the following.Setting pressStatus as true within onPresList and if it is true change backround color.But navigation to route is not working.There is a mistake
https://i.stack.imgur.com/w9YiR.png
How do I change background color onPress? Following is my code.
updated
import React, { Component } from "react";
import { Image, FlatList } from "react-native";
import {
Content,
Text,
List,
ListItem,
Icon,
Container,
Left,
Right,
Badge,
Thumbnail
} from "native-base";
import styles from "./style";
const drawerCover = require("../../imgs/quwait.jpg");
const datas = [
{
name: "Dashboard",
route: "Anatomy",
icon: require("../../imgs/dashboard.png"),
},
{
name: "Companies",
route: "Header",
icon: require("../../imgs/enterprise1.png"),
},
{
name: "Company Admin",
route: "Footer",
icon: require("../../imgs/icon1.png"),
},
{
name: "Employee",
route: "NHBadge",
icon: require("../../imgs/businessman1.png"),
},
{
name: "Employs",
route: "NHButton",
icon: require("../../imgs/employee1.png"),
},
{
name: "Announcement",
route: "NHCard",
icon: require("../../imgs/megaphone1.png"),
},
{
name: "Holiday",
route: "Check",
icon: require("../../imgs/sun-umbrella1.png"),
},
{
name: "Accounting Report",
route: "NHTypography",
icon: require("../../imgs/accounting1.png"),
},
{
name: "Invoice",
route: "NHCheckbox",
icon: require('../../imgs/approve-invoice1.png'),
},
{
name: "Settings",
route: "NHDatePicker",
icon: require('../../imgs/settings1.png'),
},
{
name: "Safety Phone Numbers",
route: "NHThumbnail",
icon: "user",
},
{
name: "NBK",
route: "NHDeckSwiper",
icon: "swap",
},
{
name: "ABK",
route: "NHFab",
icon: "help-buoy",
},
{
name: "CBK",
route: "NHForm",
icon: "call",
},
{
name: "Daily Invoice",
route: "NHIcon",
icon: "information-circle",
},
{
name: "Kolin",
route: "NHLayout",
icon: "grid",
},
{
name: "Limak",
route: "NHList",
icon: "lock",
},
{
name: "Polaytol",
route: "ListSwipe",
icon: "code-working",
},
{
name: "ACTS",
route: "NHPicker",
icon: "arrow-dropdown",
}
];
class SideBar extends Component {
constructor(props) {
super(props);
this.state = {
shadowOffsetWidth: 1,
shadowRadius: 4,
pressStatus:false
};
}
onPressList = (DATA, INDEX) => {
this.props.navigation.navigate(DATA.route);
this.setState({ pressStatus : true, selectedItem: INDEX});
}
render() {
return (
<Container>
<Content
bounces={false}
style={{ flex: 1, backgroundColor: "#fff", top: -1 }}
>
<Image source={drawerCover} style={styles.drawerCover} />
<FlatList
data={datas}
keyExtractor={(item, index) => String(index)}
renderItem={({ DATA, INDEX }) => {
<ListItem
button
noBorder
onPress={() => this.onPressList(DATA, INDEX)}
style={{
backgroundColor:
this.state.selectedItem === INDEX ? "#cde1f9" : "transparent"
}}
>
<Left>
<Image
source={DATA.icon }
style={{width:30,height:30}}
/>
<Text style={styles.text}>
{DATA.name}
</Text>
</Left>
</ListItem>}}
/>
</Content>
</Container>
);
}
}
export default SideBar;
In the App example from native Base they don't support styles for background items list. So you should change your List component from NativeBase and add a FlatList Component from react native. But you should also return the ListItem component from NativeBase and don't forget the import { FlatList } from "react-native";
You should also modify the onPressList function (I would transform it into an arrow function)
In your states you need to add the state selectedItem: 0
Everytime you press an item, your function would be called by modifying a selectedItem idex, which tells the Flatlist, which Item should get which background. I think this has to be the solution.
If it doesn't compile, make sure that you support arrow functions and that any curly braces or something like that isn't missing.
Final Code UPDATE
import React, { Component } from "react";
import { Image, FlatList } from "react-native";
import {
Content,
Text,
List,
ListItem,
Icon,
Container,
Left,
Right,
Badge,
Thumbnail
} from "native-base";
import styles from "./style";
const drawerCover = require("../../imgs/quwait.jpg");
const datas = [
{
name: "Dashboard",
route: "Anatomy",
icon: require("../../imgs/dashboard.png"),
},
{
name: "Companies",
route: "Header",
icon: require("../../imgs/enterprise1.png"),
},
{
name: "Company Admin",
route: "Footer",
icon: require("../../imgs/icon1.png"),
},
{
name: "Employee",
route: "NHBadge",
icon: require("../../imgs/businessman1.png"),
},
{
name: "Employs",
route: "NHButton",
icon: require("../../imgs/employee1.png"),
},
{
name: "Announcement",
route: "NHCard",
icon: require("../../imgs/megaphone1.png"),
},
{
name: "Holiday",
route: "Check",
icon: require("../../imgs/sun-umbrella1.png"),
},
{
name: "Accounting Report",
route: "NHTypography",
icon: require("../../imgs/accounting1.png"),
},
{
name: "Invoice",
route: "NHCheckbox",
icon: require('../../imgs/approve-invoice1.png'),
},
{
name: "Settings",
route: "NHDatePicker",
icon: require('../../imgs/settings1.png'),
},
{
name: "Safety Phone Numbers",
route: "NHThumbnail",
icon: "user",
},
{
name: "NBK",
route: "NHDeckSwiper",
icon: "swap",
},
{
name: "ABK",
route: "NHFab",
icon: "help-buoy",
},
{
name: "CBK",
route: "NHForm",
icon: "call",
},
{
name: "Daily Invoice",
route: "NHIcon",
icon: "information-circle",
},
{
name: "Kolin",
route: "NHLayout",
icon: "grid",
},
{
name: "Limak",
route: "NHList",
icon: "lock",
},
{
name: "Polaytol",
route: "ListSwipe",
icon: "code-working",
},
{
name: "ACTS",
route: "NHPicker",
icon: "arrow-dropdown",
}
];
class SideBar extends Component {
constructor(props) {
super(props);
this.state = {
shadowOffsetWidth: 1,
shadowRadius: 4,
pressStatus:false,
selectedItem:0
};
}
onPressList = (data, index) => {
this.props.navigation.navigate(data.route);
this.setState({ pressStatus : true, selectedItem: index});
}
render() {
return (
<Container>
<Content
bounces={false}
style={{ flex: 1, backgroundColor: "#fff", top: -1 }}
>
<Image source={drawerCover} style={styles.drawerCover} />
<FlatList
data={datas}
keyExtractor={(item, index) => String(index)}
extraData={this.state.selectedItem}
renderItem={({item:data, index}) => {
const { selectedItem: sd } = this.state
const localColor ={backgroundColor: sd === index ? "#cde1f9" : "transparent"}
return (
<ListItem
button
noBorder
style={localColor}
onPress={() => this.onPressList(data, index)}
>
<Left>
<Image
source={data.icon}
style={{width:30,height:30}}
/>
<Text style={styles.text}>
{data.name}
</Text>
</Left>
</ListItem>
)
}}
/>
</Content>
</Container>
);
}
}
export default SideBar;