Custom Components in a Custom Layout in react-admin e.g. appbar, sidebar doesnt' don't show the correct theme colours for all page types - material-ui

What you were expecting:
When you are dynamically pick a theme object in a custom layout based on the URL/history in react-admin resource, the correct theme is used on the every page type e.g. List, Show, Create and Edit.
What happened instead:
When you go to the resource listing page, the correct theme is used. But when you then navigate to any of the create/show or edit pages, the theme seems to default back to the base / primary one.
Steps to reproduce:
Create Custom Layout file. Overwrite the Appbar and List component (with Listbase) with custom ones too. Although I'm not sure the appbar/listbase steps are necessary.
Use the custom Layout in the Admin component.
Create some resources.
Dynamically pick a theme based on the name of the Resouce (using history).
Navigate to the listing page of the resources. They display the specific theme correctly.
Navigate to the create/show/edit pages of the resources. These, for some reason, default back to the primary theme (or primary default colours).
Related code:
At the root level code..
<Admin
authProvider={AuthProvider({ awsConfig })}
dashboard={Dashboard}
dataProvider={graphQLProvider}
history={history}
layout={Layout}
title="Home"
loginPage={CustomLogin}
// logoutButton={LogoutButton}
>
<LMSResource projection={["details"]} name="Assigment" options={{label: 'Assignments'}} create={AssignmentCreate} list={AssignmentList}
title="Generic" show={AssignmentShow} edit={AssignmentEdit} icon={AssignmentIcon}/>
In the custom Layout file (I have an array of themes to choose from that is imported from another file)
const Layout = ({
children,
dashboard,
logout,
}) => {
const [resource, setResource] = useState('');
const history = useHistory();
useEffect(() => {
const newResource = history.location.pathname.replace(/^\/|\/$/g, '').toLowerCase();
if(newResource===''){
setResource('primary');
} else if (newResource !== resource) {
setResource(newResource);
}
}, [history.location.pathname]);
function getTheme(themes){
let result = ''
var i=0;
for(i = 0; i < themes.length; i++){
if(Object.keys(themes[i])[0] === resource){
result = themes[i][resource];
}
}
return (!result ? themes[0].primary : result );
}
const theme = createMuiTheme(getTheme(themes));
const useStyles = makeStyles(() => ({
...
}));
const classes = useStyles(theme);
const dispatch = useDispatch();
const open = useSelector(state => state.admin.ui.sidebarOpen);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Grid container>
<Box className={classes.sidebar} color="grey.200" borderRight={1} item md={3} lg={2} component={Grid} display={{ xs: 'none', md: 'block' }}>
<Grid>
<Logo/>
<SchoolText color='textPrimary'/>
</Grid>
<TextField className={[classes.search,classes['search' + resource]]} id="outlined-basic" label="Search" variant="outlined" />
<Sidebar open={open} className={classes.sidebar}>
<Menu logout={logout} hasDashboard={!!dashboard} />
</Sidebar>
</Box>
<Box item component={Grid} xs={12} md={9} lg={10}>
<AppBar title={resource} logout={logout} />
<Box component={Grid} className={classes.breadcrumb} display={{ xs: 'none', md: 'block' }} borderTop={1}>
<Breadcrumbs/>
</Box>
<main>
<div className={classes.content}>
{children}
</div>
</main>
</Box>
<Notification />
</Grid>
</ThemeProvider>
);
}; export default Layout
I can post the List, Show, Create and Edit components if necessary, but I presume if one of resource components is receiving the right theme variables, then they all should?
To be clear, I mean the layout colours outside of the List, Edit, Create and Show components and in the actual Layout components i.e. the appbar background is the wrong colour, as is the sidebar etc. So how can they be right for List but wrong for Edit, Create and Show pages when it uses the same layout component for each in the resource? I don't think I have any way of controlling this?
Environment
React-admin version: 3.7.1
Last version that did not exhibit the issue (if applicable): n/a
React version: 17.0.2
Browser: all
Stack trace (in case of a JS error): n/a

Sooo. Don't mess with Regexs kids, they're dangerous. The issue was related to the different URL structure of create/edit/show vs list and therefore the theme matching didn't work..

Related

Material UI Customized styled element with theme

I'm starting to use Material UI in my project.
I have created a theme with some basic definitions, using my css variables:
import { createTheme } from '#mui/material/styles';
import theme from './_my-variables.scss';
const muiTheme = createTheme({
palette: {
primary: {
main: theme['color-blue'], // 'color-blue' is my css variable
},
},
typography: {
fontFamily: theme['brand-font'], // 'brand-font' is my css variable
},
}
I also used ThemeProvider in my App layout:
export const App = (props) => {
return (<Provider store={store}>
<ThemeProvider theme={muiTheme}>
<ConnectedRouter history={historyRef.history}>
<Layout {...props} />
</ConnectedRouter>
</ThemeProvider>
</Provider>);
};
I have a page with MUI elements, and it works fine and takes the new primary color and the new font.
<Box>
<Paper>
<Checkbox color="primary" /> <!-- I can see my new color in the page! Yay! -->
some text
</Paper>
</Box>
Now I want to create a custom element - like <MyCustomizeBox> that will have a different properties, using my css variables (for example - a specific width, defined in my variables).
How do I define them in my theme and how to use it to customize a new element?
I prefer not to user classes () because I want to create a generic elements for reusing. Also I don't want to change all Boxes in my app - only the customized ones.
I tries to use "withStyles" but I was getting the default theme instead of my customized theme and I saw on Google that I'm not support to use both "withStyles" and theme together.
At last, found the answer:
import { styled } from '#mui/system';
const MyCustomizedPaper = styled(Paper)(({ theme }) => ({
width: theme.custom.myCustomWidthCssVar
}));
<MyCustomizedPaper ></MyCustomizedPaper >

Can't figure out how to style material ui datagrid

I'm trying to style material-ui DataGrid component to justify the content in the cells. I am reading the material ui docs about styling but I don't seem to doing it correct and frankly find the docs on styling very confusing.
The doc here: https://material-ui.com/customization/components/#overriding-styles-with-classes implies I should be able to do something like this:
const StyledDataGrid = withStyles({
cellCenter: {
justifyContent: "center",
},
})(DataGrid);
<div style={{ height: 300, width: '100%' }}>
<StyledDataGrid rows={rows} columns={columns} />
</div>
However, when I do this, I don't see the style being added to the MuiDataGrid-cellCenter DOM element. Attaching a screenshot which shows the element classes. In the inspector I see that the style isn't being added (and if I add it manually I get the desired results). Am I not using the withStyles function correctly?
So after a bit more messing around, I believe the issue is that the DataGrid component does not support the classes property (which it seems most of the material ui components do). I believe the withStyles usage about is shorthand for passing the classes via the classes prop. Since the prop isn't listed in the API https://material-ui.com/api/data-grid/ I'm assuming this is why it isn't working. I confirmed that I can get the styles working by using a combination of the className parameter with descendant selection.
If someone determines I'm wrong and there is a way to get withStyles working on this component please comment.
const useStyles = makeStyles({
root: {
"& .MuiDataGrid-cellCenter": {
justifyContent: "center"
}
}
});
...
export default function X() {
const classes = useStyles();
return (
...
<DataGrid className={classes.root} checkboxSelection={true} rows={rows} columns={columns} />
...
)
}
ALTERNATIVE SOLUTION: (for others with similar issues)
If you are working within a class and cannot use hooks...
<div>
<DataGrid
rows={rows}
columns={columns}
sx={{
'&.MuiDataGrid-root .MuiDataGrid-cell:focus': {
outline: 'none',
},
}}
/>
</div>

How to handle state in Material UI Data Grid's components

I'm attempting to add search functionality to a data grid component. In order to achieve this, I'm adding an input element to the table using the components.header prop as follows (I've omitted irrelevant code):
const Table = () => {
const filterRows = (rows) => {
return rows;
};
const [searchTerm, setSearchTerm] = useState("");
const Header = () => (
<input
value={searchTerm}
onChange={(event) => setSearchTerm(event.target.value)}
/>
);
return (
<div style={{ height: 500 }}>
<DataGrid
rows={searchTerm ? filterRows(orders) : orders}
columns={orderColumns}
components={{
header: () => <Header />
}}
/>
</div>
);
};
The issue I'm having is that the input element loses focus each time a character is entered into the input in the header. Presumably, this is because it updates state, which triggers a re-render. This makes it impossible to share and access state of anything contained inside the Data Grid's components because it requires a React.FC argument and won't accept a ReactElement so the input is always re-rendered.
Am I missing something or is this actually not possible with Material UI's Data Grid? It seems like a pretty expected use-case to have something stateful in the header that we'd want to access like a controlled input component in order to use it as a kind of "Tool bar" as mentioned in the Material UI docs.
I've created a code sandbox to replicate the issue here: https://codesandbox.io/s/compassionate-keldysh-z995k?file=/src/App.js:246-726.
Cheers.

How to change expansion panel icon position to the left?

In my app, the expansion arrow has to be in the left side of the panel.
But, by default it's displaying in the right side.
This :
<ExpansionPanelSummary
className={classes.panelSummary}
expandIcon={<ExpandMoreIcon />}
IconButtonProps={{edge: 'start'}}
aria-controls='panel1a-content'
id='panel1a-header'
>
Doesn't made it.
Granted, you can't (easily) change the order in which the components appear in the HTML. However, there is a way using only CSS. ExpansionPanelSummary uses display: flex; you can therefore set the order property on the icon to make it appear to the left of the content.
This can be achieved with either useStyles or withStyles (Or possibly using plain CSS, but I haven't tried it); here's how you'd go about using the latter:
import withStyles from "#material-ui/core/styles/withStyles";
const IconLeftExpansionPanelSummary = withStyles({
expandIcon: {
order: -1
}
})(ExpansionPanelSummary);
You can then write the rest of your code using IconLeftExpansionPanelSummary instead of ExpansionPanelSummary when you want the icon to appear to the left. Don't forget to set IconButtonProps={{edge: 'start'}} on the component for proper spacing.
<AccordionSummary
className={classes.accordionSummary}
classes={{
expandIcon: classes.expandIcon,
expanded: classes.expanded
}}
IconButtonProps={{
disableRipple: true
}}
></AccordionSummary>
You can add class and use flex-direction
accordionSummary: {
flexDirection: 'row-reverse'
}
It's simple
add class on <ExpansionPanelSummary> like this
<ExpansionPanelSummary className={classes.panelSummary}>
add css against this class in jss like this
panelSummary:{flexDirection: "row-reverse"},
In case using css
add class on <ExpansionPanelSummary> like this
<ExpansionPanelSummary className="panelSummary">
add css against this class in jss like this
.panelSummary{flex-direction: row-reverse;}
you can get the expansion panel icon on left by removing it from expandIcon and add it as a children in Summary something like this
<ExpansionPanel defaultExpanded={true}>
<ExpansionPanelSummary aria-controls="panel1a-content">
{this.state.expanded ? <RemoveIcon/> : <ExpandIcon />}
<Typography component='h4' variant='h4'>My Expansion Panel</Typography>
</ExpansionPanelSummary>
<ExpansionPanelsDetails />
</ExpansionPanel>
The challenge is that the order is hardcoded into the codebase and you will not be able to use the ExpansionPanel as is.
If you look at the implementation, you will find the code as below
<div className={clsx(classes.content, { [classes.expanded]: expanded })}>{children}</div>
{expandIcon && (
<IconButton
disabled={disabled}
className={clsx(classes.expandIcon, {
[classes.expanded]: expanded,
})}
edge="end"
component="div"
tabIndex={-1}
aria-hidden
{...IconButtonProps}
>
{expandIcon}
</IconButton>
)}
As you see the <div> contains the text and then the IconButton is displayed.
So, you may have to work with what's provided out of the box or create your own Component based on what material-UI provides.
Hope that helps.
You can modify the CSS class like this:
notice the absolute position, in this way you can move the div that contains the icon whatever position you want with 'left' or 'right' properties
const useStyles = makeStyles((theme) => ({
ExpansionPanelSummaryExpandedIcon: {
'& div.MuiExpansionPanelSummary-expandIcon': {
position: 'absolute',
right: '5%',
},
}
}));
and then use in the ExpansionPanelSummary
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1-content"
id="panel1bh-header"
className={classes.ExpansionPanelSummaryExpandedIcon}
>
references:
https://cssinjs.org/?v=v10.3.0
https://v4-8-3.material-ui.com/customization/components/#overriding-styles-with-classes

How to include menu options with custom icons?

The documentation for react native popup menu mentions how to create menu options with a checkmark
const CheckedOption = (props) => (
)
I want to create menu options with custom icons. I do not have the unicode value for those icons. I created a custom MenuOptionWithIcon component and wrapped the icon and the menu option inside a view.
export class MenuOptionWithIcon
extends React.Component<IMenuOptionProps, {}> {
public static defaultProps: Partial<IMenuOptionProps> = {
disabled: false,
};
public render() {
return (
<View style={PopupMenuStyleSheet.menuOptionWithIcon}>
{this.props.icon}
<MenuOption
{...this.props}
text={this.props.text}
onSelect={this.props.onSelect}
disabled={this.props.disabled}
/>
</View>
);
}
}
But I am not able to apply customStyles to these options now. I want to increase the padding of each of these options so that the tap target is increased. Is this the right way to create a custom menu option with an icon? Or is there a way to get unicode values for the icons that I need? Thanks!
EDIT:
Based on the suggestion in the answer below, I did the following but I now only see the text in my menu option. I don't see the Icon being displayed. onSelect works, text is displayed but the icon is not displayed.
const IconOption = (props) => (
<MenuOption {...props}>
<Icon name={props.iconName} size={30} />
{props.children}
</MenuOption>
);
<MenuOptions customStyles={MenuOptionStyles}>
<IconOption
iconName='md-bookmark'
onSelect={this.onSelectSave.bind(this)}
text={MenuOptionStrings.Save}
/>
</MenuOptions>
for the icons you need special font - use e.g. react-native-vector-icons and then use the same approach as for CheckedOption:
const IconOption = ({iconName, text, ...others}) => (
<MenuOption {...others} >
<Text>
<Icon name={iconName} />
{' ' + text}
</Text>
</MenuOption>
)