Mui Tabs - How to use custom icons - reactjs

I'm new to mui and react in general, and I have 2 questions.
I'm trying to use my own SVG files as custom icons in a mui tab, but can't figure out how exactly to do that (with all the effects as using mui icon).
my code:
import { ReactComponent as SecondIcon } from "../../images/tabs/secondTab.svg";
import { ReactComponent as ThirdIcon } from "../../images/tabs/thirdTab.svg";
import { ReactComponent as FourthIcon } from "../../images/tabs/fourthTab.svg";
import FirstTabSvg from "../../images/svg/FirstTabSvg";
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
maxWidth: 500,
width: 80,
alignItems: "center",
display: "flex",
flexDirection: "column",
},
margin: {
padding: theme.spacing(2),
},
tab: {
width: 80,
},
imageIcon: {
height: '100%'
},
iconRoot: {
textAlign: 'center'
}
}));
export default function IconTabs() {
const classes = useStyles();
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<Paper square className={classes.root}>
<IconButton color="primary" size="small" className={classes.margin}>
<LogoIcon />
</IconButton>
<Divider variant="middle" style={{ alignSelf: "stretch" }} />
<Tabs
value={value}
onChange={handleChange}
// variant="fullWidth"
width="auto"
indicatorColor="primary"
textColor="primary"
aria-label="icon tabs example"
orientation="vertical"
>
<Tab icon={<FirstTabSvg />} aria-label="first" />
<Tab icon={<SecondIcon />} aria-label="second" />
<Tab icon={<ThirdIcon />} aria-label="third" />
<Tab icon={<FourthIcon />} aria-label="fourth" />
</Tabs>
</Paper>
);
}
I've tried 2 options (import as ReactComponent & SvgIcon with src)
FirstTabSvg.js (saw this as an answer in another question):
import React from 'react';
import pure from 'recompose/pure';
import { SvgIcon } from '#material-ui/core';
let FirstTabSvg = (props) => (
<SvgIcon {...props}>
<img src="../tabs/firstTab.svg" />
</SvgIcon>
);
FirstTabSvg = pure(FirstTabSvg);
FirstTabSvg.displayName = 'FirstTabSvg';
FirstTabSvg.muiName = 'SvgIcon';
export default FirstTabSvg;
result:
Tabs
as you can see, the FirstTabSvg does not render, and the ReactComponent does render but I don't know how to change the color for an active tab.
how can I change the tabs width? as you can see in the picture, the indicator is not in the same line as the tabs

Related

How to access values from context in a separate functional component

I'm trying to build a simple light mode/dark mode into my app I saw this example on Material UI for light/dark mode but I'm not sure how I can get access to the value for when the user clicks toggleColorMode in my Header component if it's being set in toggleColorMode function?
I guess my question is how can I get access to the value of light/dark mode of the context in my Header component if it's in a different function?
Here is my code.
import React, { useState, useEffect } from "react";
import MoreVertIcon from "#mui/icons-material/MoreVert";
import DarkModeIcon from "#mui/icons-material/DarkMode";
import LightModeIcon from "#mui/icons-material/LightMode";
import Paper from "#mui/material/Paper";
import { useTheme, ThemeProvider, createTheme } from "#mui/material/styles";
import IconButton from "#mui/material/IconButton";
import Navigation from "../Navigation/Navigation";
const ColorModeContext = React.createContext({ toggleColorMode: () => {} });
export const Header = (props) => {
const { mode } = props;
const theme = useTheme();
const colorMode = React.useContext(ColorModeContext);
console.log("mode is...", mode);
return (
<div className="header-container">
<Paper
elevation={3}
style={{ backgroundColor: "#1F1F1F", padding: "15px" }}
>
<div
className="header-contents"
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<div
className="logo"
style={{ display: "flex", alignItems: "center" }}
>
<img
src="/images/header-logo.png"
alt="URL Logo Shortener"
width={"50px"}
/>
<h1 style={{ color: "#ea80fc", paddingLeft: "20px" }}>
URL Shortener
</h1>
</div>
<div className="settings">
<IconButton
sx={{ ml: 1 }}
onClick={colorMode.toggleColorMode}
color="inherit"
aria-label="dark/light mode"
>
{theme.palette.mode === "dark" ? (
<DarkModeIcon
style={{
cursor: "pointer",
marginRight: "10px",
}}
/>
) : (
<LightModeIcon
style={{
cursor: "pointer",
marginRight: "10px",
}}
/>
)}
</IconButton>
<IconButton aria-label="settings">
<MoreVertIcon style={{ color: "#fff", cursor: "pointer" }} />
</IconButton>
</div>
</div>
</Paper>
{/* Navigation */}
<Navigation />
</div>
);
};
export default function ToggleColorMode() {
const [mode, setMode] = React.useState("light");
const colorMode = React.useMemo(
() => ({
toggleColorMode: () => {
setMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
},
}),
[]
);
const theme = React.useMemo(
() =>
createTheme({
palette: {
mode,
},
}),
[mode]
);
return (
<ColorModeContext.Provider value={colorMode}>
<ThemeProvider theme={theme}>
<Header mode={mode} />
</ThemeProvider>
</ColorModeContext.Provider>
);
}
Read the documentation: createContext, useContext. You need to render a ContextProvider in your parent (or top-level) component, then you can get the data in any component in the tree like const { theme } = useContext(ColorModeContext);.
You don't need to pass the mode as props, put it as one of the values in the context and access it.
Here's how you would render it in your example:
<ColorModeContext.Provider value={{colorMode, theme}}>
<Header />
</ColorModeContext.Provider>
You can pass an object inside the value in the context provider, in other word you can pass the toggle function inside your value to be consumed in the childern. thus you gain an access to change your mode state.
Note that the way changes are determined can cause some issues when passing objects as value, this might trigger unnecessary rerendering see Caveats for more info. or refer to the useContext docs
<ColorModeContext.Provider
value={{ colorMode: colorMode, toggleColorMode: toggleColorMode }}
>
<ThemeProvider theme={theme}>
<Header />
</ThemeProvider>
</ColorModeContext.Provider>

Putting gradient background using makeStyles

For some reason, it doesn't respect background: 'linear-gradient(to right, blue.200, blue.700)' under makeStyles. Why is that? All I need to do is put a gradient background on the entire space. <Container className={classes.root}> should probably be a div, what do you think?
import { useState, useEffect } from 'react';
import type { NextPage } from 'next';
import Container from '#mui/material/Container';
import Box from '#mui/material/Box';
import { DataGrid, GridColDef } from '#mui/x-data-grid';
import { createStyles, Grid, Paper, Theme, Typography } from '#mui/material';
import { makeStyles } from '#mui/styles';
import Skeleton from '#mui/material/Skeleton';
import FormOne from './../src/FormOne';
const LoadingSkeleton = () => (
<Box
sx={{
height: 'max-content',
}}
>
{[...Array(10)].map((_, index) => (
<Skeleton variant="rectangular" sx={{ my: 4, mx: 1 }} key={index} />
))}
</Box>
);
const columns: GridColDef[] = [
{ field: 'id', headerName: 'ID' },
{ field: 'title', headerName: 'Title', width: 300 },
{ field: 'body', headerName: 'Body', width: 600 },
];
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
height: '100vh',
overflow: 'auto',
background: `linear-gradient(to right, blue.200, blue.700)`,
},
})
);
const Home: NextPage = () => {
const classes = useStyles();
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
// fetch data from fake API
useEffect(() => {
setInterval(
() =>
fetch('https://jsonplaceholder.typicode.com/posts')
.then((response) => response.json())
.then((data) => {
setPosts(data);
setLoading(false);
}),
3000
);
}, []);
return (
<Container
maxWidth={false}
// sx={{
// height: '100vh',
// overflow: 'auto',
// background: `linear-gradient(to right, ${blue[200]}, ${blue[700]})`,
// }}
className={classes.root}
>
<Container component="main" maxWidth="lg" sx={{ mt: 3, mb: 3 }}>
<Grid container spacing={{ xs: 2, md: 3 }}>
<Grid item xs={6}>
<Paper sx={{ padding: 3 }}>
<Typography component="h1" variant="h4" align="center">
GUI #1
</Typography>
<FormOne />
</Paper>
</Grid>
<Grid item xs={6}>
<Paper sx={{ padding: 3 }}>
<Typography component="h1" variant="h4" align="center">
GUI #2
</Typography>
<FormOne />
</Paper>
</Grid>
<Grid item xs={12}>
<Paper sx={{ padding: 3 }}>
<DataGrid
sx={{ height: '650px' }} // either autoHeight or this
rows={posts}
columns={columns}
pageSize={10}
// autoHeight
rowsPerPageOptions={[10]}
disableSelectionOnClick
disableColumnMenu
disableColumnSelector
components={{
LoadingOverlay: LoadingSkeleton,
}}
loading={loading}
/>
</Paper>
</Grid>
</Grid>
</Container>
</Container>
);
};
export default Home;
I think its because you pass it as a string and it simply doesnt recognice what blue.200 is etc.
try:
background: `linear-gradient(to right, ${blue[200]}, ${blue[700])}`,
#Edit
You actualy need to import color that you want to use from #mui/color
import { blue } from "#mui/material/colors";
and then use it as I mentioned before
here is codesandbox preview and here is codesandbox code
hope this is what we want to achieve
Instead of using background use backgroundImage. This should fix the problem.
The code should be
backgroundImage: `linear-gradient(to right, blue[200], blue[700])`,

Dynamic URL in React

I'm working on a React project with Redux and I'm consuming a Rest API, I need to implement a functionality where when I select a project from a list and I need to load the project ID in the URL and direct to another screen where a sidebar with the options is loaded. navigation of this project.
Example: Layout
I managed to load the project's Id in the URL and retrieve this ID in the project's home screen, the problem is to store the project's Id and set this ID in the next selected URLs, for example:
path: '/project/:id/companies'
path: '/project/:id/currencies'
path: '/project/:id/settings'
List of projects:
Capture the project id and arrow the url:
href={`#/project/${row.id}/main`}
Routes:
path: '/project/:id/main',
exact: true,
name: 'projectMain',
component: RequireAuth(ProjectMain),
Retrieve ID in main
import { useParams } from 'react-router-dom';
...
const { id } = useParams();
The problem is in the sidebar, where I load a list of items with the path, I'm not able to pass the project id in this list.
Complementando a pergunta
In Sidebar I'm using useHistory(), the problem is that the path comes static by 'props' through importing a file into my template, as you can see below:
Template
import React from 'react';
import { Grid, makeStyles } from '#material-ui/core';
import {
AppContent,
AppHeader,
SidebarApp,
} from '../components/index';
import itemsProject from '../components/itemsSidebar/itemsProject';
const useStyles = makeStyles(theme => ({
appContent: {
paddingLeft: 240,
width: '100%',
backgroundColor: theme.palette.background.paper,
},
}));
const ProjectLayout = () => {
const classes = useStyles();
return (
<div className={classes.appContent}>
<AppHeader />
<Grid container direction="row">
<SidebarApp items={itemsProject} />
<AppContent />
</Grid>
</div>
);
};
export default ProjectLayout;
Sidebar:
/* eslint-disable react/jsx-no-duplicate-props */
import React from 'react';
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import Divider from '#material-ui/core/Divider';
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
import ExpandLessIcon from '#material-ui/icons/ExpandLess';
import Collapse from '#material-ui/core/Collapse';
import {
alpha,
Box,
Card,
ListSubheader,
makeStyles,
Typography,
} from '#material-ui/core';
import { useHistory, useLocation, useParams } from 'react-router-dom';
import translate from '../providers/i18n/translate';
const useStyles = makeStyles(theme => ({
sidebar: {
background: theme.palette.background.dark,
width: 240,
height: '100vh',
border: '1px solid rgba(0, 0, 0, 0.1)',
display: 'flex',
flexDirection: 'column',
position: 'absolute',
paddingTop: 64,
top: 0,
left: 0,
},
sidebarItem: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
},
sidebarItemContent: {
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
display: 'flex',
alignItems: 'center',
width: '100%',
},
sidebarItemIcon: {
marginRight: 6,
},
sidebarItemText: {
width: '100%',
},
sidebarItemExpandArrow: {
fontSize: '1.2rem !important',
},
sidebarItemExpandArrowExpanded: {
fontSize: '1.2rem !important',
color: theme.palette.primary.main,
fontWeight: 'bold',
},
active: {
background: alpha(theme.palette.primary.light, 0.2),
},
}));
function SidebarItem({ depthStep = 10, depth = 0, expanded, item, ...rest }) {
const [collapsed, setCollapsed] = React.useState(true);
const { label, items, Icon, onClick: onClickProp } = item;
const classes = useStyles();
const history = useHistory();
const location = useLocation();
function toggleCollapse() {
setCollapsed(prevValue => !prevValue);
}
function onClick(e) {
if (Array.isArray(items)) {
toggleCollapse();
}
if (onClickProp) {
onClickProp(e, item);
history.push(item.path);
}
}
let expandIcon;
if (Array.isArray(items) && items.length) {
expandIcon = !collapsed ? (
<>
<ExpandLessIcon className={classes.sidebarItemExpandArrowExpanded} />
</>
) : (
<ExpandMoreIcon className={classes.sidebarItemExpandArrow} />
);
}
return (
<>
<ListItem
className={classes.sidebarItem}
onClick={onClick}
button
dense
className={location.pathname === item.path ? classes.active : null}
{...rest}
>
<div
style={{ paddingLeft: depth * depthStep }}
className={classes.sidebarItemContent}
>
{Icon && (
<Icon
className={classes.sidebarItemIcon}
fontSize="small"
color="primary"
/>
)}
<div className={classes.sidebarItemText}>{label}</div>
</div>
{expandIcon}
</ListItem>
<Collapse in={!collapsed} timeout="auto" unmountOnExit>
{Array.isArray(items) ? (
<List disablePadding dense>
{items.map((subItem, index) => (
<React.Fragment key={`${subItem.name}${index}`}>
{subItem === 'divider' ? (
<Divider style={{ margin: '6px 0' }} />
) : (
<SidebarItem
depth={depth + 1}
depthStep={depthStep}
item={subItem}
/>
)}
</React.Fragment>
))}
</List>
) : null}
</Collapse>
</>
);
}
function Sidebar({ items, depthStep, depth, expanded }) {
const classes = useStyles();
const { key } = useParams();
return (
<Card elevation={0} className={classes.sidebar}>
<List
disablePadding
dense
subheader={
<ListSubheader component="div" id="nested-list-subheader">
{translate('sidebarMenuSettings')}
<Typography>
<Box>{key}</Box>
</Typography>
</ListSubheader>
}
>
{items.map((sidebarItem, index) => (
<React.Fragment key={`${sidebarItem.name}${index}`}>
{sidebarItem === 'divider' ? (
<Divider style={{ margin: '6px 0' }} />
) : (
<SidebarItem
depthStep={depthStep}
depth={depth}
expanded={expanded}
item={sidebarItem}
/>
)}
</React.Fragment>
))}
</List>
</Card>
);
}
export default Sidebar;
Sidebar list items
function onClick(e, item) {}
const itemsProject = [
{
name: 'companies',
label: translate('sidebarProjectCompanies'),
Icon: CompanyIcon,
path: '/project/:id/companies',
onClick,
}
{
name: 'currencies',
label: translate('sidebarProjectCurrencies'),
Icon: CurrencyIcon,
path: '/project/:id/currencies',
onClick,
}
];
export default itemsProject;
How can I pass the ID variable on the Sidebar list items?
I thank you for your help!
You can use ES6 template literals as follows.
path: `/project/${id}/companies`
Since you already defined your path, you just need to use useHistory and navigate to the new link
import { useHistory } from 'react-router';
...
const history = useHistory();
...
// call this whenever you want to navigate
history.push(`/project/${id}/currencies`);

Material-UI Swipeable Drawer Does not translate smoothly

import { ThemeProvider } from '#material-ui/core/styles'
import React, { useState } from 'react'
import { Link } from 'react-router-dom'
import AppBar from '#material-ui/core/AppBar'
import Toolbar from '#material-ui/core/Toolbar'
import Tabs from '#material-ui/core/Tabs'
import Tab from '#material-ui/core/Tab'
import IconButton from '#material-ui/core/IconButton'
import MenuIcon from '#material-ui/icons/Menu'
import SwipeableDrawer from '#material-ui/core/SwipeableDrawer'
import { useMediaQuery, makeStyles } from '#material-ui/core'
import { useTheme } from '#material-ui/core/styles'
import { ListItem, ListItemText } from '#material-ui/core'
import logo from './logo.png'
const linksData = [
{
id: 1,
name: 'home',
path: '/',
},
{
id: 2,
name: 'portfolio',
path: '/portfolio',
},
{
id: 3,
name: 'services',
path: '/services',
},
{
id: 4,
name: 'about',
path: '/about',
},
]
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: '1',
},
contentItems: {
width: '100%',
height: '100%',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '0.5rem',
},
logo: {
width: '100%',
height: '4rem',
objectFit: 'cover',
},
logoContainer: {
flexBasis: '1',
display: 'flex',
alignItems: 'center',
},
linksContainer: {
flexBasis: '3',
},
link: {
textDecoration: 'none',
minWidth: 10,
},
}))
function App() {
const theme = useTheme()
const webView = useMediaQuery(theme.breakpoints.down('md'))
const classes = useStyles()
const [value, setValue] = useState(1)
const [openDrawer, setOpenDrawer] = useState(false)
const handleChange = (e, value) => setValue(value)
const iOS = process.browser && /iPad|iPhone|iPod/.test(navigator.userAgent)
return (
<>
<Router>
<ThemeProvider theme={theme}>
<Switch>
<Route path='/'>
<div className={classes.root}>
<AppBar>
<Toolbar>
<nav className={classes.contentItems}>
<div className={classes.logoContainer}>
<img src={logo} alt='logo' className={classes.logo} />
</div>
<Tabs value={value} onChange={handleChange}>
{linksData.map(({ path, name, id }) => {
console.log(path)
return (
<Tab
key={id}
value={id}
component={Link}
to={path.toString()}
className={classes.link}
label={name}
></Tab>
)
})}
</Tabs>
{webView && (
<IconButton onClick={() => setOpenDrawer(true)}>
<MenuIcon />
</IconButton>
)}
{openDrawer && (
<SwipeableDrawer
disableBackdropTransition={!iOS}
disableDiscovery={iOS}
open={openDrawer}
onOpen={() => setOpenDrawer(true)}
onClose={() => setOpenDrawer(false)}
>
<ListItem>
<ListItemText>hello</ListItemText>
<br />
<ListItemText>hello</ListItemText>
</ListItem>
</SwipeableDrawer>
)}
</nav>
</Toolbar>
</AppBar>
</div>
</Route>
</Switch>
</ThemeProvider>
</Router>
</>
)
}
export default App
I'm trying to implement a Swipeable Drawer, the drawer is visible, the fade out transition is smooth as well, however, the drawer does not translate smoothly, The drawer slides in and out without any transition style applied to it( which should be applied). One interesting case is that in case the "openDrawer" state is set to 'true', during initialization, the drawer translates smoothly, but beyond that, the transitions of the drawer are not smooth.
Here's a CodeSandBox link: https://codesandbox.io/s/xenodochial-ritchie-owen9
At first when I ran your sandbox and from what I can see it's running fine... I don't notice any jankiness when toggling the sidebar open/close.
BUT
As I was suspecting with the conditional rendering, when I took it out the sidebar transition is butter smooth and not nearly so "instant".
{openDrawer && (
<SwipeableDrawer
disableBackdropTransition={!iOS}
disableDiscovery={iOS}
open={openDrawer}
onOpen={() => setOpenDrawer(true)}
onClose={() => setOpenDrawer(false)}
>
<ListItem>
<ListItemText>hello</ListItemText>
<br />
<ListItemText>hello</ListItemText>
</ListItem>
</SwipeableDrawer>
)}
Remove the conditional rendering.
<SwipeableDrawer
disableBackdropTransition={!iOS}
disableDiscovery={iOS}
open={openDrawer}
onOpen={() => setOpenDrawer(true)}
onClose={() => setOpenDrawer(false)}
>
<ListItem>
<ListItemText>hello</ListItemText>
<br />
<ListItemText>hello</ListItemText>
</ListItem>
</SwipeableDrawer>

Underline shows when using materal-ui InputBase component

I'm using material-ui InputBase component to create a search bar and it shows an underline. However when I write the same code in another project there is no underline..
Any clues to what the problem might be?
Here is the code for the search bar
import React from 'react';
import { IconButton, InputBase, Paper } from '#material-ui/core';
import { makeStyles } from '#material-ui/core/styles'
import SearchIcon from '#material-ui/icons/Search'
const useStyles = makeStyles((theme) => ({
root: {
padding: '2px 4px',
display: 'flex'
alignItems: 'center'
width: 400
},
input: {
marginLeft: theme.spacing(1),
flex: 1,
},
iconButton: {
padding: 10
}
}));
export default function SearchBar() {
const classes = useStyles();
return (
<Paper className={classes.root}>
<InputBase
className{classes.input}
placeholder='Search..'
inputProps={{ 'aria-label': 'search' }}
/>
<IconButton
type='submit'
className={classes.iconButton}
aria-label='search'
>
<SearchIcon />
</IconButton>
</Paper>
)
}

Resources