I need to implement a <BackButton /> in react-admin for example when I open show page for a resource I need able to back to the list page.
Can you guide me to implement this?
I'm not familiar with react-admin routing mechanism.
Now I'm using this component in my edit form actions props:
const MyActions = ({ basePath, data, resource }) => (
<CardActions>
<ShowButton basePath={basePath} record={data} />
<CloneButton basePath={basePath} record={data} />
{/* Need <BackButton /> here */}
</CardActions>
);
export const BookEdit = (props) => (
<Edit actions={<MyActions />} {...props}>
<SimpleForm>
...
</SimpleForm>
</Edit>
);
You can use react-router-redux's goBack() function to achieve this.
For example, your button component will look something like this:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Button from '#material-ui/core/Button';
import { goBack } from 'react-router-redux';
class BackButton extends Component {
handleClick = () => {
this.props.goBack();
};
render() {
return <Button variant="contained" color="primary" onClick={this.handleClick}>Go Back</Button>;
}
}
export default connect(null, {
goBack,
})(BackButton);
Now use that button component in your CardActions.
You can get help from an example which uses react-router-redux's push() function in a similar way from the official docs.
Create a back button. This one will pass props and children (text) and uses react-router directly, which I think makes more sense and keeps your code simple.
// BackButton.js
import React from 'react'
import Button from '#material-ui/core/Button'
import { withRouter } from 'react-router'
const BackButton = ({ history: { goBack }, children, ...props }) => (
<Button {...props} onClick={goBack}>
{children}
</Button>
)
export default withRouter(BackButton)
Example usage:
import { Toolbar, SaveButton } from 'react-admin'
import BackButton from './BackButton'
const SomeToolbar = props => (
<Toolbar {...props}>
<SaveButton />
<BackButton
variant='outlined'
color='secondary'
style={{ marginLeft: '1rem' }}
>
Cancel
</BackButton>
</Toolbar>
)
The complete code is below.
//BackButton.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import compose from 'recompose/compose';
import { withStyles, createStyles } from '#material-ui/core/styles';
import { translate } from 'ra-core';
import Button from '#material-ui/core/Button';
import ArrowBack from '#material-ui/icons/ArrowBack';
import classnames from 'classnames';
import { fade } from '#material-ui/core/styles/colorManipulator';
const styles = theme =>
createStyles({
deleteButton: {
color: theme.palette.error.main,
'&:hover': {
backgroundColor: fade(theme.palette.error.main, 0.12),
// Reset on mouse devices
'#media (hover: none)': {
backgroundColor: 'transparent',
},
},
},
});
const sanitizeRestProps = ({
basePath,
className,
classes,
label,
invalid,
variant,
translate,
handleSubmit,
handleSubmitWithRedirect,
submitOnEnter,
record,
redirect,
resource,
locale,
...rest
}) => rest;
class BackButton extends Component {
static contextTypes = {
router: () => true, // replace with PropTypes.object if you use them
}
static propTypes = {
label: PropTypes.string,
refreshView: PropTypes.func.isRequired,
icon: PropTypes.element,
};
static defaultProps = {
label: 'ra.action.back',
icon: <ArrowBack />,
};
render() {
const {
className,
classes = {},
invalid,
label = 'ra.action.back',
pristine,
redirect,
saving,
submitOnEnter,
translate,
icon,
onClick,
...rest
} = this.props;
return (
<Button
onClick={this.context.router.history.goBack}
label={label}
className={classnames(
'ra-back-button',
classes.backButton,
className
)}
key="button"
{...sanitizeRestProps(rest)}>
{icon} {label && translate(label, { _: label })}
</Button>
)
}
}
const enhance = compose(
withStyles(styles),
translate
);
export default enhance(BackButton);
//Toolbar.js
import React from 'react';
import {
Toolbar,
SaveButton,
DeleteButton,
} from 'react-admin';
import { withStyles } from '#material-ui/core';
import BackButton from './BackButton'
const toolbarStyles = {
toolbar: {
display: 'flex',
justifyContent: 'space-between',
},
};
export const CustomEditToolbar = withStyles(toolbarStyles)(props => (
<Toolbar {...props}>
<SaveButton/>
<DeleteButton/>
<BackButton/>
</Toolbar>
));
export const CustomCreateToolbar = withStyles(toolbarStyles)(props => (
<Toolbar {...props}>
<SaveButton/>
<BackButton/>
</Toolbar>
));
Related
I am attempting to recreate the theme toggle feature that the material-ui website.
My Github Repo: https://github.com/jonnyg23/flask-rest-ecommerce/tree/next-app-migration
So far, I have noticed that material-ui's website uses a cookie called paletteType in order to store the client's theme choice. I understand that a Context Provider should be used to set the cookie, however, my nav-bar implementation has an issue changing themes after the second click of my theme toggle button.
Any help would be greatly appreciated, thank you.
CustomThemeProvider.js:
import React, { createContext, useState } from "react";
import { ThemeProvider } from "#material-ui/core/styles";
import getTheme from "../themes";
import Cookie from "js-cookie";
export const CustomThemeContext = createContext({
// Set the default theme and setter.
appTheme: "light",
setTheme: null,
});
const CustomThemeProvider = ({ children, initialAppTheme }) => {
// State to hold selected theme
const [themeName, _setThemeName] = useState(initialAppTheme);
// Retrieve theme object by theme name
const theme = getTheme(themeName);
// Wrap setThemeName to store new theme names as cookie.
const setThemeName = (name) => {
// console.log("CustomThemeProvider, SetThemeName", name);
Cookie.set("appTheme", name);
_setThemeName(name);
};
const contextValue = {
appTheme: themeName,
setTheme: setThemeName,
};
return (
<CustomThemeContext.Provider value={contextValue}>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</CustomThemeContext.Provider>
);
};
export default CustomThemeProvider;
_app.js:
import "../styles/globals.css";
import React, { useContext, useEffect } from "react";
import PropTypes from "prop-types";
import { useAuth0 } from "#auth0/auth0-react";
import Head from "next/head";
import { Provider as NextAuthProvider } from "next-auth/client";
import { makeStyles } from "#material-ui/core/styles";
import CssBaseline from "#material-ui/core/CssBaseline";
import Auth0ProviderWithHistory from "../auth/auth0-provider-with-history";
import CustomThemeProvider, {
CustomThemeContext,
} from "../context/CustomThemeProvider";
export default function App({ Component, pageProps }) {
// const { isLoading } = useAuth0();
const ThemeContext = useContext(CustomThemeContext);
useEffect(() => {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
return (
<React.Fragment>
<Head>
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width"
/>
</Head>
<CustomThemeProvider initialAppTheme={ThemeContext.appTheme}>
<Auth0ProviderWithHistory>
<NextAuthProvider session={pageProps.session}>
<CssBaseline />
<Component {...pageProps} />
</NextAuthProvider>
</Auth0ProviderWithHistory>
</CustomThemeProvider>
</React.Fragment>
);
}
App.propTypes = {
Component: PropTypes.elementType.isRequired,
pageProps: PropTypes.object.isRequired,
};
ThemeModeToggle.js:
import React, { useContext } from "react";
import { IconButton } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
import Brightness5TwoToneIcon from "#material-ui/icons/Brightness5TwoTone";
import Brightness2TwoToneIcon from "#material-ui/icons/Brightness2TwoTone";
import { CustomThemeContext } from "../context/CustomThemeProvider";
const useStyles = makeStyles((theme) => ({
light: {
color: theme.palette.secondary.main,
},
dark: {
color: theme.palette.secondary.main,
},
}));
const ThemeModeToggle = ({ fontSize }) => {
const classes = useStyles();
const { appTheme, setTheme } = useContext(CustomThemeContext);
// console.log("ThemeModeToggle", appTheme);
const handleThemeChange = (appTheme, setTheme) => {
if (appTheme === "light") {
setTheme("dark");
} else {
setTheme("light");
}
};
return (
<IconButton onClick={() => handleThemeChange(appTheme, setTheme)}>
{appTheme === "light" ? (
<Brightness5TwoToneIcon fontSize={fontSize} className={classes.light} />
) : (
<Brightness2TwoToneIcon fontSize={fontSize} className={classes.dark} />
)}
</IconButton>
);
};
export default ThemeModeToggle;
Solved, I found the issue!
It turns out that my page components such as StorefrontIcon and the AppBar colors had to be explicitly added with className={classes.NAME} or with style={{ backroundColor: theme.palette.primary.main }} for example. Both methods are shown below in my nav-bar.js file.
import React from "react";
import { AppBar, Grid, Container, Toolbar } from "#material-ui/core";
import { makeStyles, useTheme } from "#material-ui/core/styles";
import StorefrontIcon from "#material-ui/icons/Storefront";
import MainNav from "./main-nav";
import AuthNav from "./auth-nav";
import ThemeModeToggle from "./ThemeModeToggle";
import { Desktop, SmallScreen } from "./Responsive";
import HamburgerMenu from "./HamburgerMenu";
const useStyles = makeStyles((theme) => ({
root: {
[theme.breakpoints.down("sm")]: {
padding: 0,
},
},
icon: {
color: theme.palette.primary.contrastText,
},
}));
const NavBar = () => {
const classes = useStyles();
const theme = useTheme();
return (
<AppBar
position="static"
elevation={3}
style={{ backgroundColor: theme.palette.primary.main }}
>
<Toolbar>
<Container className={classes.root}>
<Grid container justify="center" alignItems="center" spacing={2}>
<Desktop>
<Grid item xs={1}>
<StorefrontIcon fontSize="large" className={classes.icon} />
</Grid>
<Grid item xs={7}>
<MainNav />
</Grid>
<Grid item xs={3}>
<AuthNav />
</Grid>
<Grid item xs={1}>
<ThemeModeToggle fontSize="large" />
</Grid>
</Desktop>
<SmallScreen>
<Grid item xs={2}>
<StorefrontIcon fontSize="medium" className={classes.icon} />
</Grid>
<Grid container item xs={10} justify="flex-end">
<HamburgerMenu />
</Grid>
</SmallScreen>
</Grid>
</Container>
</Toolbar>
</AppBar>
);
};
export default NavBar;
How can I pass makeStyle classes from parent component to child component and combine them with the makeStyle classes in the child component? E.g. as below adding the breakpoint hiding to the child component style.
Example child component:
import React from "react"
import { Button } from "#material-ui/core"
import { makeStyles } from "#material-ui/core/styles"
const useStyles = makeStyles(theme => ({
button: {
background: "#000",
color: "white",
//lots of other css here so we dont want to repeat it in the parent component
},
}))
export default function PrimaryButton(props) {
const classes = useStyles()
const { children, fullWidth = false } = props
return (
<Button
fullWidth={fullWidth}
className={classes.button}
variant="contained"
>
{children}
</Button>
)
}
Example parent component:
import React from "react"
import { PrimaryButton } from "components/PrimaryButton"
import { makeStyles } from "#material-ui/core/styles"
const useStyles = makeStyles(theme => ({
primaryButton: {
display: "inline-block",
[theme.breakpoints.down("sm")]: {
display: "none",
},
},
}))
export default function PrimaryButton(props) {
const classes = useStyles()
return (
<PrimaryButton
className={classes.primaryButton}
>
Button text
</PrimaryButton>
)
}
clsx is used internally within Material-UI and is a convenient utility for combining multiple class names. In your child component, you can grab className from the props and then use className={clsx(className, classes.button)} in the Button it renders:
import React from "react";
import { Button } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
import clsx from "clsx";
const useStyles = makeStyles(theme => ({
button: {
background: "#000",
color: "white"
}
}));
export default function PrimaryButton(props) {
const classes = useStyles();
const { children, className, fullWidth = false } = props;
return (
<Button
fullWidth={fullWidth}
className={clsx(className, classes.button)}
variant="contained"
>
{children}
</Button>
);
}
I am converting the React + Material UI + Firebase project found on the Material UI doc's page. I am trying to open the SignUp Dialog (i.e. SignUp Modal). Here is a simplified version of the 4 corresponding files and I need help opening up the sign up modal.
App.js
import React, { useState } from 'react';
import { ThemeProvider } from '#material-ui/core/styles';
import Navbar from './components/Navbar'
import DialogHost from './components/DialogHost';
import Loading from './components/Loading'
import theme from './theme';
function App() {
const [signedIn] = useState(false)
const [ready] = useState(true) //!toggle for testing
const [dialog, setDialog] = useState({
isOpenSignUp: false,
isOpenSignIn: false
});
const openDialog = e => {
setDialog({...dialog,[e.target.name]: true});
}
const closeDialog = e => {
setDialog({...dialog,[e.target.name]: false});
}
return (
<ThemeProvider theme={theme}>
{!ready &&
<Loading />
}
{ready &&
<>
<Navbar
signedIn={signedIn}
onSignUpClick={openDialog}
onSignInClick={openDialog}
/>
<DialogHost
signedIn={signedIn}
dialogs={
{
signUpDialog: {
dialogProps: {
open: dialog.isOpenSignUp,
//onClose below hasn't been converted... not entirely sure how
onClose: (callback) => {
this.closeDialog('signUpDialog');
if (callback && typeof callback === 'function') {
callback();
}
}
}
},
}
}
/>
</>
}
</ThemeProvider>
);
}
export default App;
Navbar.js
import React from 'react'
import PropTypes from 'prop-types';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import Box from '#material-ui/core/Box';
import Button from '#material-ui/core/Button';
const Navbar = ({ signedIn, onSignUpClick, onSignInClick }) => {
return (
<AppBar color="primary" position="static">
<Toolbar variant="regular">
<Box flexGrow={1}>
<Typography color="inherit" variant="h6">{process.env.REACT_APP_NAME}</Typography>
</Box>
{!signedIn &&
<>
<Box mr={1}>
<Button name="isOpenSignUp" color="secondary" variant="contained" onClick={onSignUpClick}>Sign Up</Button> //GAVE THE BUTTON NAMES, BUT THIS DOESN'T SEEM CORRECT...
</Box>
<Button name="isOpenSignIn" color="secondary" variant="contained" onClick={onSignInClick}>Sign In</Button>
</>
}
</Toolbar>
</AppBar>
)
}
Navbar.defaultProps = {
signedIn: false
};
Navbar.propTypes = {
signedIn: PropTypes.bool.isRequired
};
export default Navbar
DialogHost.js
import React from 'react'
import PropTypes from 'prop-types';
import Hidden from '#material-ui/core/Hidden';
import SignUpDialog from '../../pages/SignUpDialog';
const DialogHost = ({ signedIn, dialogs }) => {
const signUpDialog = dialogs.signUpDialog;
return (
<>
<Hidden xsDown>
{!signedIn &&
<>
<SignUpDialog
dialogProps={signUpDialog.dialogProps}
{...signUpDialog.props}
/>
</>
}
</Hidden>
<Hidden smUp>
{!signedIn &&
<>
<SignUpDialog
dialogProps={{
fullScreen: true,
...signUpDialog.dialogProps
}}
{...signUpDialog.props}
/>
</>
}
</Hidden>
</>
)
}
DialogHost.defaultProps = {
signedIn: false
};
DialogHost.propTypes = {
signedIn: PropTypes.bool.isRequired,
dialogs: PropTypes.object.isRequired
};
export default DialogHost
I also have a SignUpDialog.js file, but all it is is rendering a functional component with a title of 'Sign Up'
I am trying to use a navbar from the Material UI collection however the component was written as a function and was using hooks. I'm trying to convert the component to a HOC (class) and I am having issues with accessing the theme in the component. Theme in the component in undefined
const styles = theme => ({
root: {
display: "flex"
},
});
<IconButton onClick={this.handleDrawerClose}>
{theme.direction === "ltr" ? (
<ChevronLeftIcon />
) : (
<ChevronRightIcon />
)}
</IconButton>
Try this:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core';
import Paper from './Paper';
const styles = () => ({
root: {
display: 'flex'
}
});
const Bubble = props => {
const { classes } = props;
return (
<IconButton className={classes.root} onClick={this.handleDrawerClose}></IconButton>
);
};
export default withStyles(styles)(Bubble);
I have a <Button /> component and an <Icon/> component.
I try to implement a button with an icon.
The Button.jsx story:
import React from "react";
import { storiesOf } from "#storybook/react";
import Button from "../components/Button";
import Icon from "../components/Icon/Index";
import { iconTypes } from "../components/Icon/Index";
storiesOf("Button/Primary", module)
.add("With icon", () => (
<Button><Icon type={iconTypes.arrowRight}/></Button>
))
That works fine but I would like the api for a Button with an icon to be-
<Button icon={icons.arrow}>Click Me</Button>
How can I achieve that?
The Icon.jsx story:
import React from "react";
import { storiesOf } from "#storybook/react";
import Icon from "../components/Icon/Index";
import { iconTypes } from "../components/Icon/Index";
storiesOf("Icon", module)
.add("Arrow Right", () => (
<Icon type={iconTypes.arrowRight}>
</Icon>
))
.add("Arrow Left", () => (
<Icon type={iconTypes.arrowLeft}>
</Icon>
));
The <Button /> component:
import React from 'react';
import { css, cx } from 'emotion';
import colors from '../styles/colors';
export default function Button({
children,
...props
}) {
const mergedStyles = cx(
// css
);
// other css stuff
return (
<button {...props} disabled={disabled} className={mergedStyles}>
{children}
</button>
);
And <Icon /> component:
import React from "react";
import { css } from 'emotion';
import ArrowRight from "./arrow-right2.svg";
import ArrowLeft from "./arrow-left2.svg";
export const iconTypes = {
arrowRight: 'ARROW_RIGHT',
arrowLeft: 'ARROW_LEFT',
}
const iconSrc = {
ARROW_RIGHT: ArrowRight,
ARROW_LEFT: ArrowLeft,
}
const circleStyles = css({
width: 24,
height: 24,
borderRadius: "50%",
backgroundColor: "#f7f7f9",
display: "flex",
justifyContent: "center"
});
export default function Icon({ type }) {
return (
<div className={circleStyles}>
<img src={iconSrc[type]} />
</div>
)
};
Any help would be appreciated.
import React from 'react';
import {css, cx} from 'emotion';
import colors from '../styles/colors';
//import your ICON component & make sure your path is right
import Icon from "./../Icon";
export default function Button({
children,
disabled,
icon,
...props
}) {
const mergedStyles = cx(//your css);
return (
<button {...props} disabled={disabled} className={mergedStyles}>
// If icon prop is provided then render ICON component
{icon && <Icon type={icon}/>}
//Other children
{children}
</button>
);
}
in render of Button, you can do something like that :
Button.js:
render(){
const { icon } = this.props
return(
<Button>
{icon && <Icon type={icon}/>}
<Button>
)
}