Influence tab order of Material UI controls - reactjs

I have an app built up with React and Material UI. Within one view it is possible to have several text fields and several buttons. Now, when I have the focus on one text field and then press Tab I cannot reliably anticipate which one of the controls will be the next one to get the focus. I want to first tab through all the text fields and then secondly tab through all the buttons.
<DialogContent>
<DialogContentText>
The username and password that were used are incorrect. Please provide the correct credentials in order to login to the API.
<Stepper activeStep={this.state.credentialsStep} orientation='vertical'>
{
this.steps.map((label, index) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
<StepContent>
<Typography>{this.stepContent[index]}</Typography>
{this.stepAction[index]}
<Grid container direction='row' className='m-t-26'>
<Button color='primary'
onClick={() => {
this.state.credentialsStep === 0 ? this.onClickCancel() : this.onClickBack();
}}>
{this.state.credentialsStep === 0 ? 'Cancel' : 'Back'}
</Button>
<Button variant='contained'
color='primary'
onClick={() => {
this.state.credentialsStep === this.steps.length - 1 ? this.onClickLogin() : this.onClickNext();
}}>
{this.state.credentialsStep === this.steps.length - 1 ? 'Login' : 'Next'}
</Button>
</Grid>
</StepContent>
</Step>
))
}
</Stepper>
</DialogContentText>
</DialogContent>
Is there a way to set the tab order of controls?

You can control this with the tabIndex property, but you may be better off to figure out how to have the elements appear in the source in the order you would want the focus to go.
I have found this resource handy: https://bitsofco.de/how-and-when-to-use-the-tabindex-attribute/
When to use a positive tabindex value
There is almost no reason to
ever use a positive value to tabindex, and it is actually considered
an anti-pattern. If you’re finding the need to use this value to
change the order in which elements become focusable, it is likely that
what you actually need to do is change the source order of the HTML
elements.
One of the problems with explicitly controlling tabindex order is that any elements with a positive value are going to come before any other focusable elements that you haven't explicitly put a tabindex on. This means that you could end up with very confusing focus order if you miss any elements that you would want in the mix.
If you want to have the button on the right come before the button on the left in the focus order, there are various CSS options that would allow the button on the right to come first in the source order.
If, however, you decide that explicitly specifying the tabindex is your best option, here is an example showing how to do this for TextField and Button:
import React from "react";
import ReactDOM from "react-dom";
import TextField from "#material-ui/core/TextField";
import Button from "#material-ui/core/Button";
function App() {
return (
<div className="App">
<TextField label="1" inputProps={{ tabIndex: "1" }} />
<br />
<TextField label="3" inputProps={{ tabIndex: "3" }} />
<br />
<TextField label="2" inputProps={{ tabIndex: "2" }} />
<br />
<Button tabIndex="5">Button 5</Button>
<Button tabIndex="4">Button 4</Button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

You may want to use the html attribute tabindex. This allows you to specify the order that tabbing will go through in your form. You can read more about it here and I've put a small example below, setting the tab index of your button to #1
<StepContent>
<Typography>{this.stepContent[index]}</Typography>
{this.stepAction[index]}
<Grid container direction="row" className="m-t-26">
<Button
tabIndex="1" // This will make the button the first tab index for the form.
color="primary"
onClick={() => {
this.state.credentialsStep === 0
? this.onClickCancel()
: this.onClickBack();
}}
>
{this.state.credentialsStep === 0 ? "Cancel" : "Back"}
</Button>
<Button
variant="contained"
color="primary"
onClick={() => {
this.state.credentialsStep === this.steps.length - 1
? this.onClickLogin()
: this.onClickNext();
}}
>
{this.state.credentialsStep === this.steps.length - 1 ? "Login" : "Next"}
</Button>
</Grid>
</StepContent>;

You can use a css trick to render the buttons in reverse order, but with css to reverse the buttons in UI.
<DialogContent>
<DialogContentText>
The username and password that were used are incorrect. Please provide the correct credentials in order to login to the API.
<Stepper activeStep={this.state.credentialsStep} orientation='vertical'>
{
this.steps.map((label, index) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
<StepContent>
<Typography>{this.stepContent[index]}</Typography>
{this.stepAction[index]}
<Grid container direction='row' className='m-t-26'>
// Box wrapper added <Box style={{ display: 'flex', flexDirection: 'row-reverse', justifyContent: 'flex-end' }}>
// First Button is now "Next in JSX <Button variant='contained'
color='primary'
onClick={() => {
this.state.credentialsStep === this.steps.length - 1 ? this.onClickLogin() : this.onClickNext();
}}>
{this.state.credentialsStep === this.steps.length - 1 ? 'Login' : 'Next'}
</Button>
<Button color='primary'
onClick={() => {
this.state.credentialsStep === 0 ? this.onClickCancel() : this.onClickBack();
}}>
{this.state.credentialsStep === 0 ? 'Cancel' : 'Back'}
</Button>
</Box>
</Grid>
</StepContent>
</Step>
))
}
</Stepper>
</DialogContentText>
</DialogContent>

Related

How to use custom isOpen with a chakra component

I am relatively new to react and not sure why isOpen is not working as expected.
Please see the code below for the example that I am working with
I have a menu icon that is using isOpen to open navlinks:
const { isOpen, onOpen, onClose } = useDisclosure();
<IconButton variant={"unstyled"} bgColor={"white"} color={"black"} size={"s"} icon={isOpen ? <Hamburger size={"24"} /> : <Hamburger size={"24"} />} aria-label={"Open Menu"} display={{ md: "none" }} onClick={isOpen ? onClose : onOpen} />
{isOpen ? (
<Box color={"#b8860b"} pb={4} display={{ md: "none" }}>
<Stack as={"nav"} spacing={5}>
{Links.map(link => (
<Link key={link.name} href={link.route}>
<Flex paddingBottom="40px" h="40px" borderBottom="1px" borderColor="black" justifyContent={'left'}>
<Flex paddingLeft={"10px"} paddingTop={"3%"}> {link.icon}</Flex>
<Text p={2} color={"black"} >
{link.name}
</Text>
</Flex>
</Link>
))}
</Stack>
</Box>
) : null}
When I try using a custom isOpen to open a drawer component, i just cant get it to work..
What am I doing wrong?:
const { isOpenMenu, onOpenMenu, onCloseMenu } = useDisclosure()
<Button
bgColor={"white"}
onClick={isOpenMenu}
>
<BsCart4 size={"26px"} color={"black"} />
{cartItemCount > 0 && <Badge ml='1' fontSize='0.9em' colorScheme='green'>{cartItemCount}</Badge>}
</Button>
<Drawer
isOpen={isOpenMenu}
placement='right'
onClose={onCloseMenu}
finalFocusRef={btnRef}
>
<DrawerOverlay />
<DrawerContent>
<DrawerCloseButton />
<DrawerHeader>Create your account</DrawerHeader>
<DrawerBody>
<Input placeholder='Type here...' />
</DrawerBody>
<DrawerFooter>
<Button variant='outline' mr={3} onClick={onCloseMenu}>
Cancel
</Button>
<Button colorScheme='blue'>Save</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
```
When I use isOpen for the drawer it works fine so I thought having another isOpen but custom would open the drawer but its not working as expected.
Can someone help understand why my thinking isnt right based on how to use isOpen correctly?
It seems like only isOpen works when I switch it from using the menu bar and drawer
First of all, if you want to use custom names to the useDisclosure states, you have to set them like this:
const { isOpen: isOpenMenu, onOpen: onOpenMenu, onClose: onCloseMenu } = useDisclosure()
Also, the onClose prop of the drawer component is just a event handler, dont put your onCloseMenu function ther, just call it when you want to close the drawer, as you made on the cancel button, example:
<Button onClick={onCloseMenu} ... />

Multi-select text input on screen after closing modal

I have a modal and after I close the modal I want to show on the screen the options that were selected on the modal.
My code is here: https://codesandbox.io/s/react-select-xdpj7?file=/src/CreatableInputOnly.tsx
On this fragment below I am calling the part that handles the text on the modal on CreatableInputOnly. The part that handles the dropdown is on the ReactSelect call:
<Fragment>
<Button onClick={handleClickOpen}>ModalButton</Button>
<div>Selected options on the modal were: </div>
<Dialog
maxWidth={"sm"}
fullWidth={true}
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
classes={{
paperFullWidth: classes.paperFullWidth
}}
>
<DialogTitle id="alert-dialog-title">Dialog</DialogTitle>
<DialogContent
classes={{
root: classes.dialogContentRoot
}}
>
<Grid container spacing={2}>
<Grid item xs={6}>
<FormControl style={{ width: "100%" }}>
<ReactSelect isMulti={true} options={country} />
</FormControl>
</Grid>
</Grid>
<Grid container spacing={2}>
<CreatableInputOnly />
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} variant="contained">
Close
</Button>
</DialogActions>
</Dialog>
</Fragment>
You can create a state variable in the ModalTest.tsx and pass the setter function to the select component reactMaterialSelect.tsx.
const [selectedValues, setSelectedValues] = React.useState([]);
Then, you can update the code, which will display the selected options. Its just a simple map function printing a label of each index item.
<div>
Selected options on the modal were:{" "}
{selectedValues?.length
? selectedValues.map((item, idx) =>
idx !== 0 ? `, ${item.label}` : item.label
)
: ""}
</div>
Update the component part to send the additional prop of state setter value.
<ReactSelect
handleSelectValues={setSelectedValues}
isMulti={true}
options={country}
/>
In reactMaterialSelect.tsx, the change function are updated to change the state in the parent variable.
function handleChangeSingle(value) {
setSingle(value);
handleSelectValues([value]);
}
function handleChangeMulti(value) {
setMulti(value);
handleSelectValues(value);
}
To manage the createdInputs, a new state variable is added.
const [createAbleInputs, setCreateAbleInputs] = React.useState([]);
A variable to combine the results of both states.
const combinedArray =
createAbleInputs === null
? [...selectedValues]
: [...selectedValues, ...createAbleInputs];
Then the compoent createableInputsOnly is updated to change the state in the modal based on the changes in the component.
Updated sandbox link.

How to make the Collapse position to bottom-right after clicking on expand icon in AntD

I am using AntD Collapse for displaying a list of items after expand icon is clicked.
I want the position of expandIcon to go to bottom-right after all the list of the data when expand icon is clicked (just like in google news), but found only two options (left|right) for 'expandIconPosition', no option for top or bottom.
How can we align the expandIcon to bottom-right, when expand icon is clicked?
Few lines from the code for reference:
<Collapse
ghost
style={{ marginTop: "-1vh" }}
expandIcon={({ isActive }) => (
<DownOutlined
style={{ marginTop: "-2vh" }}
rotate={isActive ? 180 : 0}
/>
)}
expandIconPosition="right"
>
<Panel>
<div>
{list()} //list of items
</div>
</Panel>
</Collapse>
Here's one possible solution. Make Collapse a controlled component by specifying activeKey prop and then the value of it will be based on state. Then, by tracking the activeKeys state you can now do a conditional rendering (hide and show) on icons:
const [activePanelKeys, setActivePanelKeys] = useState([]);
const handlePanelIconClick = (panelKey, makeActive) => {
if (makeActive) {
setActivePanelKeys([...activePanelKeys, panelKey]);
} else {
setActivePanelKeys(activePanelKeys.filter((aPK) => aPK !== panelKey));
}
};
<Collapse
activeKey={activePanelKeys}
expandIconPosition="right"
expandIcon={() => <DownOutlined />}
// expandIcon={(panelProps) => (
// <DownOutlined
// onClick={() => handlePanelIconClick(panelProps.panelKey, true)}
// />
// )}
onChange={e => setActivePanelKeys(e)} //if you want to click only icon, comment this and uncomment the above expandedIcon
>
<Panel
header="This is panel header 1"
key="p1"
showArrow={activePanelKeys.indexOf("p1") === -1}
>
<div>{text}</div>
{activePanelKeys.indexOf("p1") !== -1 && (
<div style={{ textAlign: "right", marginTop: 10 }}>
<UpOutlined onClick={() => handlePanelIconClick("p1", false)} />
</div>
)}
</Panel>
{/* <PanelContent header="This is panel header 2" key="p2">
{text}
</PanelContent> */}
</Collapse>;
Here is the complete sample code:
Note: I tried to make a reusable Panel component but it seems that the reveal animation were gone. You can uncomment the commented PanelContent on the code to see the difference.
Hope that I hit what you want.

How to show only icon to the particular user when we click on it using reactjs

I'm trying to show icon when it is hovered and clicked on the particular User but here when i click on the icon of particular user then i could see for the other users also showing the icon without hovering or clicking on it. Can anyone help me in this query?
Here is code:
<Card>
{Data.map(user => (
<CardHeader
key={user.id}
className={classes.header}
avatar={<Avatar aria-label="recipe">R</Avatar>}
action={
<div className={this.state.menu && classes.menu}>
<IconButton
id="simple-menu"
className={classes.showIcon}
aria-label="settings"
aria-controls="simple-menu"
onClick={this.handleClick}
>
<MoreVertIcon />
</IconButton>
<Menu
style={{ marginTop: "35px" }}
id="simple-menu"
keepMounted
anchorEl={this.state.menu}
open={Boolean(this.state.menu)}
onClose={this.handleClose}
>
<MenuItem onClick={this.handleClose}>View</MenuItem>
<MenuItem onClick={this.handleClose}>hide</MenuItem>
</Menu>
</div>
}
title={user.title}
subheader={user.subheader}
/>
))}
</Card>
https://codesandbox.io/s/material-ui-menu-button-visibility-fix-hujx4
For ex: if i click on Shrimp and Chorizo Paella User (dot icon) then we could see same icon visible in the other user Sherlock holmes. My objective is to show the icon whenever i hover the user card, and when we click on the icon, then the icon should visible only on the respective user card but not in other User card
The problem is you are using the same state for all of your cards. You are deciding on
the basis of state.menu for all cards resulting in the same behaviour no matter on which card the event has occured. As a result if the state.menu is set to value by hovering on any card & the menu of all the cards is visible. To solve this you will need to uniquely identify your card. For that you will need to manage state for each card. You can achieve it like this:
Create a state for each card identified by user.id using the componentDidMount lifecycle hook before return the <Card> component.
let cardStates = {}
Data.map(user => {
cardStates[user.id] = { menu: null }
});
this.setState(cardStates);
...
Convert your handle events to callbacks
<Menu
style={{ marginTop: "35px" }}
id="simple-menu"
keepMounted
anchorEl={this.state[user.id].menu}
open={Boolean(this.state[user.id].menu)}
onClose={this.handleClose}
>
<MenuItem
onClick={e => {
this.handleClose(e, user.id);
}}
>
View
</MenuItem>
<MenuItem
onClick={() => {
this.handleClose(user.id);
}}
>
hide
</MenuItem>
</Menu>
Manipulate state in handlers by accepting your user.id as argument.
handleClick = (e, uid) => {
let newCardState = {}
newCardState[uid] = { menu: e.target }
this.setState(newCardState);
};
handleClose = (uid) => {
let newCardState = {}
newCardState[uid] = { menu: null}
this.setState(newCardState);
};

Is it possible to make component style dependent on state without re-rendering?

I am working with the component below - I need the button to be contained when the filter state corresponds to it and outlined otherwise.
The way I do it now, the component re-renders every time the state is changed - I see why this is happening. However, I wonder if there would be a way to achieve the same functionality without referring to the filter state in this component? It is not a great user experience if the buttons disappear every time the state changes.
function FilterButtons({ filter, setFilter }) {
const classes = useStyles();
return (
<div className={classes.heroButtons}>
<Grid container spacing={2} justify="center">
<Grid item>
<Button
variant={filter === "All" ? "contained" : "outlined"}
color="primary"
onClick={() => setFilter("All")}
>
All
</Button>
</Grid>
<Grid item>
<Button
variant={filter === "Blue" ? "contained" : "outlined"}
color="primary"
onClick={() => setFilter("Blue")}
>
Blue
</Button>
</Grid>
<Grid item>
<Button
variant={filter === "Red" ? "contained" : "outlined"}
color="primary"
onClick={() => setFilter("Red")}
>
Red
</Button>
</Grid>
<Grid item>
<Button
variant={filter === "Green" ? "contained" : "outlined"}
color="primary"
onClick={() => setFilter("Green")}
>
Green
</Button>
</Grid>
</Grid>
</div>
);
}
You should have a look at styled components. This is exactly what you're describing in your question. You can pass a props to a styled components and it will use this props to update its style without rendering.

Resources