React styled-component button does not receive focus
This is some code I inherited (see below). There are rows with a title and corresponding button (Bookmark). Using the tab button I can focus on the title. Upon another tab the focus moves to the styled button, but there is no focus ring. On the next tab the focus goes to the next title with a focus ring, and so on.
Why is the styled.button not receiving focus?
If the IconWrapper styled component (listed below) is replaced with <button></button> then the
icon receives focus and has a focus ring.
<button
ref={node => (this.removeBookmarkBtns[i] = node)}
aria-label="Remove bookmark"
onClick={() => {this.markForRemoval(bookmark, i);}}
>
<BookmarkIcon />
</button>
Inherited code
import styled from "styled-components";
<div>
... irrelevant stuff
<IconWrapper
ref={node => (this.removeBookmarkBtns[i] = node)}
aria-label="Remove bookmark"
onClick={() => {this.markForRemoval(bookmark, i);}}
>
<BookmarkIcon />
</IconWrapper>
... irrelevant stuff
</div>
The associated button styled-components
const IconWrapper = styled.button`
color: ${theme.colors.primary};
font-size: 2em;
cursor: pointer;
margin-left: 10px;
background-color: Transparent;
background-repeat: no-repeat;
border: none;
outline: none;
`;
const BookmarkIcon = styled(Icons.BookmarkCheck)`
display: block;
overflow: visible;
`;
Icon.BookmarkCheck
Icons.BookmarkCheck = props => (
<SVG {...props}>
<path d="m256 512a254.4 ...
</SVG>
);
Have you tried adding tabIndex={0}?
<button
ref={node => (this.removeBookmarkBtns[i] = node)}
aria-label="Remove bookmark"
onClick={() => {this.markForRemoval(bookmark, i);}}
tabIndex={0}
/>
After a bit of rubber ducking it dawned on me that it isn't the styled-component causing the problem, but the CSS of the styled-component.
Remove outline: none and of course it works.
Related
I am using Accordion in next js. I am following this (react-accessible-accordion) package.
Currently it is getting collapse when main div is getting clicked. What i want to achieve is According should be collapse when click on arrow icon not on main div.
If there is an alternate way of doing this please let me know.
UPDATE
Here is the code sandbox
What i want to achieve is explained here
Thanks!
first I had to modify the styles so the icon is not taking the full width.
Accordion.css
.accordion__icon {
/* margin-left: auto; */
transition: transform 0.6s ease;
}
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
display: flex;
align-items: center;
border: none;
outline: none;
transition: background-color 0.6s ease;
/* add flex display and justify content */
display: flex;
justify-content: space-between;
}
Add onClick to the main SVG and pass it through the props.
Chevron.js
// pass an onClick props to the main SVG
function Chevron(props) {
return (
<svg
className={props.className}
height={props.height}
width={props.width}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
onClick={props.onClick}
>
<path
fill={props.fill}
d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"
/>
</svg>
);
}
export default Chevron;
Finally make the props.onClick of the Chevron component handle the onClick event not its Button parent component.
Accordion.js
...
// move the onClick from the button to the Chevron component
return (
<div className="accordion__section">
<button className={`accordion ${setActive}`}>
<p className="accordion__title">{props.title}</p>
<Chevron
onClick={toggleAccordion}
className={`${setRotate}`}
width={10}
fill={"#777"}
/>
</button>
...)
full sandbox link
I am using react, styled-components.
When state(visible) is set to true, DropMenu box1 and box2 will be displayed.
We want the ArrowDown icon to flip upward when state is true, and downward when false.
I also want to apply an animation when flipping it.
I want to add an animation like the Dropdown in the following site.
Reference site
code
import "./styles.css";
import styled from "styled-components";
import React, { useState, useCallback } from "react";
import { ArrowDown } from "./ArrowDown";
const Item = styled.div<{ active?: boolean }>`
height: 40px;
width: 300px;
padding: 0px 30px;
&:hover {
background: #fafbfb;
}
`;
const DropMenu = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
color: #899098;
width: 100%;
height: 100%;
font-size: 14px;
font-weight: bold;
gap: 12px;
:hover {
color: gray;
}
div {
display: flex;
align-items: center;
gap: 12px;
}
`;
const DropText = styled.div`
padding-left: 32px;
`;
export const App = () => {
const [visible, setVisible] = useState(false);
const handleDropVisibleChange = useCallback(() => {
setVisible((prevVisible) => !prevVisible);
}, [visible]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Item onClick={handleDropVisibleChange}>
<DropMenu>
<div>
<span>Menu</span>
</div>
<ArrowDown />
</DropMenu>
</Item>
{visible && (
<div style={{ transition: "all 0.5s ease" }}>
<Item>
<DropMenu>
<DropText>box1</DropText>
</DropMenu>
</Item>
<Item>
<DropMenu>
<DropText>box2</DropText>
</DropMenu>
</Item>
</div>
)}
</div>
);
};
export default App;
TLDR
Change your MenuItem component warpper to something like
const DropMenuWrapper = styled.div<{ visible: boolean }>`
transition: all 0.5s ease;
opacity: ${(props) => (props.visible ? 1 : 0)};
`;
replace the visibility switch mechanism with following
- {visible && (
- <div style={{ transition: "all 0.5s ease" }}>
+ <DropMenuWrapper visible={visible}>
similar action can be added to the arrow-down icon also with style
(The ArrowDown SVG icon must accept style if it is custom written component)
<ArrowDown
style={{
transition: "all 0.5s ease",
transform: `rotate(${visible ? 0 : "0.5turn"})`
}}
/>
Why this happened:
When a component (sub-component/element) is mounted in react, it starts a complete life cycle toward browser paint.
So it is must have the property which causes the element to animate, for example, I added the opacity transition to the example itself, forcing it to animate in the first look and in disappearing.
Although it comes with some performance cost of having unseen elements still in the dom (but not visible), making it bad for accessibility too, it is the simplest way to achieve this behavior.
Consider this example If you have an animated element, does it show the animation if you refresh the browser if the answer is yes, it will show animation in react too.
Another way of doing some animation in react.
Using third-party library react-transtion-group which is heavily used in lots of packages e.g. Material-UI.
In this case you can also trigger the end event and start to unmount the component as the animation disappears and end completely.
Using framer motion
If you want to take your understanding of what is needed for the transition when the component is unmounted and removed from aka dom, I highly encourage you to read the animation section of svelte docuementation
What I did, what might look stupid to more advanced developers was implement a simple check that would switch icons.
Note: This doesn't have an animation, though. It's just a simple switcharoo
define state in component
const [isOpen, setIsOpen] = useState(false);
Check whether icon is open or closed, if open, ExpandLessIcon, if closed ExpandMoreIcon.
<ExpandLessIcon
onClick={() => {
setIsOpen(!isOpen);
}}
/>
) : (
<ExpandMoreIcon
onClick={() => {
setIsOpen(!isOpen);
}}
/>
)}
The way it works is, once clicked, it'll just flip the true false state over and over, which in turn will change icons.
I have a slight problem with Material UI input, I want to change the design of it using styled components, but I came across an issue. This is my code:
import React from "react";
import styled from "styled-components";
import Input from "#mui/material/Input";
import InputAdornment from "#mui/material/InputAdornment";
import { BiSearch } from "react-icons/bi";
const InputContainer = styled(Input)`
width: 350px;
height: 42px;
border-radius: 2px;
border: 1px solid #c0c0c0;
`;
const SearchIcon = styled(BiSearch)`
color: #c0c0c0;
margin-left: 14px;
`;
const InputComponent = ({ placeholder, type }) => {
return (
<div>
<InputContainer
placeholder={placeholder}
startAdornment={
type === "Search" ? (
<InputAdornment position="start">
<SearchIcon size="20" />
</InputAdornment>
) : (
""
)
}
/>
</div>
);
};
export default InputComponent;
What I want to do:
Change placeholder size
When user hovers or clicks on input there is no bottom border like there is now, basically I want to remove bottom border
How do I achieve this?
Change placeholder size
For this, you can target the input element and change the font size. e.g.
const InputContainer = styled(Input)`
width: 350px;
height: 42px;
border-radius: 2px;
border: 1px solid #c0c0c0;
input::placeholder {
font-size: 20px;
}
`;
When user hovers or clicks on input there is no bottom border like
there is now, basically I want to remove bottom border
For this, you can use the disableUnderline prop. e.g the updated code will be
<InputContainer
placeholder={placeholder}
disableUnderline
startAdornment={
type === "Search" ? (
<InputAdornment position="start">
<SearchIcon size="20" />
</InputAdornment>
) : (
""
)
}
/>
i have this image:
so if you can see i have this image and i have created onMouseEnter and onMouseOut so the red cross appears when i get the mouse inside and disappear when the mouse is outside the image. however, this red cross also is a button and has a functionality and whenever i hover on the button it disappears even though i set the state right. my problem is that the button disappear and i don't want that cz it has functionality:
code.js:
const handleOnMouseHover = () => {
const isToggle = true
setToggleDiv(isToggle)
}
const handleOnMouseOut = () => {
const isToggle = false
setToggleDiv(isToggle)
}
<div>
<h3>preview items</h3>
<div className="image">
<img src={Url} onMouseEnter={handleOnMouseHover} onMouseOut={handleOnMouseOut} />
<div>{photoTitle}</div>
{toggleDiv === true?
<div className="delete" type="button" onClick={() => handleDelete(photoId)}>X</div>
:null}
</div>
</div>
code.css:
.image {
position: relative;
width: 50px;
margin: 0px auto;
}
.image .delete {
background-color: red;
width: 30px;
padding: 10px;
color: white;
display: block;
cursor: pointer;
position: absolute;
top: 0;
right: 0;
}
hope you can help
Try to use the onMouseEnter on the div with class "image" instead of the img itself.
Because when you hover the button then the hover from image is removed and onMouseEnter will not work.
Hope it helps
As I understand you need to move your onMouseEnter onMouseOut to the container of image and button, like that:
<h3>preview items</h3>
<div className="image" onMouseEnter={handleOnMouseHover} onMouseOut={handleOnMouseOut}>
<img src={Url} />
<div>{photoTitle}</div>
{toggleDiv === true?
<div className="delete" type="button" onClick={() => handleDelete(photoId)}>X</div>
:null}
</div>
The Problem
I have a simple List of components that renders a title and an simple <hr /> after it.
The list gets rendered correctly and every title has it's corresponding <hr /> except the first one
Here is a screenshot how it looks:
I don't have any custom styles in my project except this one (for a rich text editor called Draft.js:
.editor {
box-sizing: border-box;
border: 1px solid #ddd;
cursor: text;
padding: 16px;
border-radius: 2px;
margin-bottom: 2em;
box-shadow: inset 0px 1px 8px -3px #ababab;
background: #fefefe;
}
.editor :global(.public-DraftEditor-content) {
min-height: 140px;
}
Other then that, no css files.
Here is the code that renders the list (it gets called in the parent for every list item I have):
return (
<div>
<h2>{props.title}</h2>
<p>Test</p>
<hr />
{renderPosts()}
</div>
);
Why is this happening? I played around with it in the dev tools and the missing<hr /> is actually there, but not visible but the styling seems ok. It makes no sense to me because the other hr's are there. Screenshot from the Dev Tools:
I found the problem.
It's another Bootstrap Component that overlapped the first element like #Gonzalo said.
This was the faulty code from the parent component:
return (
<div style={{ marginTop: 50 }}>
{/* TODO: implement AddRoom Component */}
{/* <OverlayTrigger
trigger="focus"
placement="top"
overlay={this.popoverTop()}
>
<Button>
<FontAwesomeIcon icon={faPlus} />
</Button>
</OverlayTrigger> */}
{this.renderRooms()}
</div>
);
I uncommented it and now I see the <hr /> I think now I have to give the OverlayTrigger just a bit margin and I'm guchi.