Material ui TreeItems change style conditionally based - reactjs

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

Related

How to show dropdown options in fluent UI breadcrumbs

i wanted to show dropdown in fluent ui breadcrumbs help on building the code. please refer below image
Reference image copied from azure devops
To show the dropdown menu at breadcrumbs, you need CommandBarButton as the trigger of the dropdown and menuProps that contain the menu's item objects and pass the CommandBarButton at Breadcrumb's last item onRenderContent event handler.
Don't forget to memoize both breadcrumb's items and dropdown menuProps.
import { CommandBarButton } from "#fluentui/react/lib/Button";
...
const menuProps: IContextualMenuProps = React.useMemo(() => {
return {
items: [
{
key: "def",
text: "Dev",
onClick: () => setEnvironment("Dev"),
onRenderContent: () => {
return <MenuItem label="Dev" environment={environment} />;
}
},
...
]
};
}, [environment, setEnvironment]);
const items: IBreadcrumbItem[] = React.useMemo(() => {
return [
{ text: "Files", key: "Files", onClick: _onBreadcrumbItemClicked },
{ text: "Folder 1", key: "f1", onClick: _onBreadcrumbItemClicked },
...
{ text: "Folder 6", key: "f6", onClick: _onBreadcrumbItemClicked },
{
text: "Folder 11",
key: "f11",
onClick: _onBreadcrumbItemClicked,
isCurrentItem: true,
onRenderContent: () => {
return (
<CommandBarButton
style={{
fontSize: 18,
fontWeight: "bold",
height: "30px",
padding: 0,
background: "transparent"
}}
text={environment}
iconProps={null}
disabled={false}
checked={false}
menuProps={menuProps}
/>
);
}
}
];
}, [environment, menuProps]);
This is the complete code:

In Material UI how can I dynamically pass down the color to my style?

New to React and Material UI I'm stumped how to properly pass down a color dynamically I'm pulling from a JSON file and I'm not finding a good reference in my searches, StackOverflow or in the docs.
Research
Material-ui hoverColor for MenuItem component?
How can I access a hover state in reactjs?
How do I change Material-UI MenuItem background on hover change from AutoComplete props?
Material UI inline styling - specific component colours
Attempt
Given the JSON file:
[
{
"label": "Home",
"href": "/",
"colorHover": ""
},
{
"label": "Monday",
"href": "/mon",
"colorHover": "#35c5bd"
},
{
"label": "Tuesday",
"href": "/tues",
"colorHover": "#fa8b25"
},
{
"label": "Wednesday",
"href": "/wed",
"colorHover": "#f26531"
}
]
I bring it into my component and loop through it with:
const getDrawerChoices = () => {
return headersData.map(({ label, href, colorHover }) => {
const colorChange = colorHover === '' ? 'inherit' : colorHover
return (
<Link
{...{
component: RouterLink,
to: href,
color: 'inherit',
style: { textDecoration: 'none' },
key: label,
}}
>
<MenuItem className={menuItem.hover(colorChange)}>{label}</MenuItem>
</Link>
)
})
}
Material UI Styles:
const useStyles = makeStyles(colorChange => ({
menuItem: {
'&:hover': {
backgroundColor: `${colorChange} !important`,
},
},
}))
but I'm thrown an error. How can I pass a color value dynamically to useStyles or do I need to do it inline with the ?
You have to create a custom makeStyle passing a prop to every key as follows:
const useStyles = makeStyles({
root: (props) => ({
"&:hover": {
backgroundColor: props.hoverColor
}
})
});
I'll leave a sandbox link with what I tested and the link to the docs. Anything else I can do let me know.

Rendering Material-UI icons from an array

Background info:
I'm using react and material-ui.
To keep the code clean, I populate menu items from a const array, like so:
const menuItems = [
{ label: "Home", path: "/home" },
{ label: "Accounts", path: "/accounts" },
{ label: "Organizations", path: "/organizations" },
];
Each item in the array is an object containing a label and a redirect path. I map over the items when rendering. Very basic.
Problem:
I would like to include a material-ui icon component in the menuItems array so the icon can be rendered next to the label. But I can't find a way to reference the icons by a name string
https://material-ui.com/components/material-icons/
Possible solutions:
put the icon component into a string:
{ label: "Accounts", path: "/accounts" }, icon: "<AccountBox/>"} but then I somehow need to evaluate the string into jsx. I don't know how.
Make a react functional component which renders a different icon depending on a prop, for example: <IconSwitch icon = {"accountIcon"} /> and hard-code different icons inside the RFC. Not pretty, but should work.
Punt and use different icons such as svg icons or font icons that can referenced by a name string.
Any suggestions on how to do this?
Thanks
Icon Font
You can use the Icon component. https://material-ui.com/components/icons/#icon-font-icons
To use an icon simply wrap the icon name (font ligature) with the Icon component, for example:
import Icon from '#material-ui/core/Icon';
<Icon>star</Icon>
https://codesandbox.io/s/material-demo-forked-sj66h?file=/demo.tsx
Assuming you set up your menu items with the appropriate icon ligatures:
const menuItems = [
{ label: "Home", path: "/home", icon: "home" },
{ label: "Accounts", path: "/accounts", icon: "account_circle" },
{ label: "Organizations", path: "/organizations", icon: "settings" }
];
Then you can map over them:
{menuItems.map(({ label, icon }) => {
return (
<span key={label}>
{label} <Icon>{icon}</Icon>
</span>
);
})}
SVG Icons
If you want to use SVG icons instead of basic icons, I'd recommend pulling only the SVG icons you plan to use in order to allow the icons you aren't using to be tree-shaken from the resulting bundle. The ability to tree shake is a good reason to use SVG icons over font icons.
import { Home, AccountCircle, Settings, AddCircle } from "#material-ui/icons";
If you want to allow user input of all icons or aren't aware ahead of time which icons will be displayed, you can import everything from #material-ui/icons as in Jonathan's answer.
If you aren't putting the list of icons into something that needs to be able to be stringified (i.e. Redux/sent through an API call) then you can just directly put the icons into the array and render them:
const menuItems: MenuItem[] = [
{ label: "Home", path: "/home", icon: <Home /> },
{ label: "Accounts", path: "/accounts", icon: <AccountCircle /> },
{ label: "Organizations", path: "/organizations", icon: <Settings /> }
];
// Rendering:
{menuItems.map(({ label, icon }) => {
return (
<span key={label}>
{label} {icon}
</span>
);
})}
If you are going to put the Icons somewhere that needs to be stringified, the above won't work, so I'd recommend putting the icons you want to use into an object to map them. That way you have a string to icon map.
Example: https://codesandbox.io/s/material-icons-svg-udcv3?file=/demo.tsx
import { Home, AccountCircle, Settings, AddCircle } from "#material-ui/icons";
const icons = {
Home,
AccountCircle,
Settings
};
In the case of the example above (i.e. rendering the icons from an array)
interface MenuItem {
label: string;
path: string;
icon: keyof typeof icons;
}
const menuItems: MenuItem[] = [
{ label: "Home", path: "/home", icon: "Home" },
{ label: "Accounts", path: "/accounts", icon: "AccountCircle" },
{ label: "Organizations", path: "/organizations", icon: "Settings" }
];
// Rendering:
{menuItems.map(({ label, icon }) => {
const Icon = icons[icon];
return (
<span key={label}>
{label} <Icon />
</span>
);
})}
You can import all from #material-ui/icons and than create an Icon component dynamically:
import React from 'react'
import * as icons from '#material-ui/icons'
interface MenuItem {
label: string,
icon: keyof typeof icons,
path: string
}
export function Menu() {
const menuItems: MenuItem[] = [
{ label: 'Home', path: './home', icon: 'Home' },
{ label: 'Accounts', path: './accounts', icon: 'AccountCircle' },
{ label: 'Organizations', path: './organizations', icon: 'Settings' }
]
return (
<>
{menuItems.map(menuItem => {
const Icon = icons[menuItem.icon]
return (
<span key={menuItem.path}>
{menuItem.label} <Icon />
</span>
)
})}
</>
)
}
// I have better way to avoid all of this other hustle .
// 1: Make Every icon in Array which is in Jsx from to simple name.
// Ex:
[
{ Name: "New", Icon: <HomeIcon /> },
{ Name: "JS Mastery", Icon: <CodeIcon /> },
{ Name: "Coding", Icon: <CodeIcon /> },
{ Name: "ReactJS", Icon: <CodeIcon /> },
{ Name: "NextJS", Icon: <CodeIcon /> },
]
to
[
({ Name: "New", Icon: HomeIcon },
{ Name: "JS Mastery", Icon: CodeIcon },
{ Name: "Coding", Icon: CodeIcon },
{ Name: "ReactJS", Icon: CodeIcon })
];
// 2: Remember Using Object keys name as capital ,here:- "Name , Icon" not "name , icon".
// 3: Now Simply use : -
{
categories.map(({ Name, Icon }) => (
<button key={Name}>
<span>{Name}</span>
<span> {<Icon/>} </span>
</button>
));
}
//use icon in this cleaver way

react-select change singleValue color based on options

I would like to modify the 'selected' singleValue color in my styles object based on the options that are provided.
const statusOptions = [
{ value: "NEW", label: i18n._(t`NEW`) },
{ value: "DNC", label: i18n._(t`DNC`) },
{ value: "WON", label: i18n._(t`WON`) },
{ value: "LOST", label: i18n._(t`LOST`) },
];
For example, if the option selected in "NEW", I want the font color to be red, if it's "WON", then green, and so on. I am having trouble putting an if statement into the styles object. I see that its simple to put a ternary statement in, but how to you add more "complex" logic?
const customStyles = {
...
singleValue: (provided) => ({
...provided,
color: 'red' <----- something like if('NEW') { color: 'green' } etc..
})
};
Use an style map object:
import React from 'react';
import Select from 'react-select';
const statusOptions = [
{ value: 'NEW', label: 'NEW' },
{ value: 'DNC', label: 'DNC' },
{ value: 'WON', label: 'WON' },
{ value: 'LOST', label: 'LOST' },
];
const styleMap = {
NEW: 'red',
DNC: 'blue',
};
const colourStyles = {
singleValue: (provided, { data }) => ({
...provided,
color: styleMap[data.value] ? styleMap[data.value] : 'defaultColor',
// specify a fallback color here for those values not accounted for in the styleMap
}),
};
export default function SelectColorThing() {
return (
<Select
options={statusOptions}
styles={colourStyles}
/>
);
}

How to show selected item in list with a different color in react native using native base ui kit?

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;

Resources