React modal component receiving same class - reactjs

I have a react Modal component that I have made. I can pass a prop that makes it full screen or define a width. It all works fine, but i'm having an issue reusing it when the first modal is open. The 2nd component takes the same class as the 1st, so it makes the max-width 100%.
For example, I want to have a full screen modal, and show a 2nd modal at 50% size on top when i click a link.
I am using styled components which i think is where the issue may be happening. Im just not sure how to approach it.
Modal component:
<ModalContainer visible={visible} fullScreen={fullScreen}>
<ModalDialog role="dialog" width={width}>
<ModalBody>{children}</ModalBody>
</ModalDialog>
</ModalContainer>
Styles
export const ModalDialog = styled.div`
width: 100%;
max-width: ${({ width }) => width};
`;
export const ModalContainer = styled.div`
visibility: hidden;
opacity: 0;
display: none;
${({ visible }) =>
visible &&
css`
visibility: visible;
opacity: 1;
display: flex;
`}
${({ fullScreen }) =>
fullScreen &&
css`
width: 100vw;
height: 100vh;
overflow: none;
${ModalDialog} {
box-shadow: none;
max-width: 100%;
}
`}
`}
The class of the first modal for dialog is what is also being used for the 2nd even though that does not have the full screen prop.

Related

Styled Components Injects wrong classes on wrong elements

I'm witnessing a weird behavior when in styled-components with SSR in remix.run
I have a ProductCard Component that renders a normal product card with styled-components
ProductCard.tsx
import Button from "../Button";
function ProductCard({ product }: props) {
return (
<>
<Wrapper>
....
<ButtonsWrapper>
<Cart
onClick={addToCart}
mode={addedToCart ? "secondary" : "primary"}
disabled={loading}
key="cart-button"
>
{addedToCart ? "Added!" : "Add to cart"}
{loading && <LoadingSpinner src="/images/responses/loader.svg" />}
</Cart>
<ShareButton mode="secondary" aria-label="share">
<Icon id="share" />
</ShareButton>
</ButtonsWrapper>
</Wrapper>
</>
);
}
const Cart = styled(Button)`
flex: 1.1;
display: flex;
justify-content: center;
gap: 10px;
`;
const ShareButton = styled(Button)`
padding: 0.9rem;
`;
const Wrapper = styled.div`
--border-radius: ${clamp(15, 20)};
--columnGap: ${clamp(20, 30)};
display: flex;
flex-direction: column;
gap: var(--columnGap);
justify-content: space-between;
width: 100%;
height: 100%;
margin: auto;
background-color: var(--azure-15);
padding: 1.9rem 1.5rem;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow-lg);
border: var(--border-lg);
`;
const ButtonsWrapper = styled.div`
display: flex;
justify-content: space-between;
gap: 0.625rem;
`;
export default ProductCard;
Button.tsx
const Button = styled.button<{ mode: "primary" | "secondary" | "dark" }>`
display: grid;
/* justify-content: center; */
align-items: center;
text-align: center;
color: var(--mintCream);
padding: ${clamp(9, 10)} ${clamp(20, 30)}; // this clamp function just generates the css clamp func with calculating the values with some equations
box-shadow: var(--box-shadow-md);
border: var(--border-md);
border-radius: 12px;
text-decoration: none;
cursor: pointer;
transition: all 500ms ease;
font-size: ${clamp(13, 16)};
&:disabled {
cursor: not-allowed;
opacity: 0.7;
}
#media (hover: hover) and (pointer: fine) {
&:hover:enabled {
transform: translateY(-2px); }
}
width: fit-content;
`;
The normal render of this Component is as follows
But when navigating to another path and returning to it on / , it renders like this
This problem only happens in production and works fine on local server...
when inspecting elements, I find that the class name of the Cart Component is also injected into the ShareButton Element
I can't find an explanation for this problem and it gets weirder... When I swap the order of the variables Cart and ShareButton or swap them with the Wrapper Element, some other weird behaviors happen like the one below
In this case, the class name of the Cart Component got injected on the parent elemnt of the parent element of the ProductCard Component
I've probably hit on 4 of these rendering issues but all of them share the same problem, the class name of the Cart Components gets injected on a wrong dom element, whether it's a parent or a sibiling
You can view the first weird behaviour here https://store.ieeenu.com
You will find the product component on the root path, navigate to some path like categories/circuits-1-ecen101 and return to the root and you will see the issue
also, you can review the second weird behavior in a previous build here
https://ieee-nu-store-r243eocii-omarkhled.vercel.app/
I just changed the initialization order of the Cart and ShareButton Components as I said earlier
I don't know whether this problem is from styled-components or from remix (this is the first time for me using remix), it's mentioned here https://github.com/remix-run/remix/issues/1032 that the lack of the babel-plugin-styled-components in remix.run introduces some problems in rehydration but I'm not sure that this is the issue I'm facing...
Thanks for reading this till the end and excuse my English, I'm not a native speaker :"

Fullscreen detection with react styled-components

I'm using React and styled-components. I would like to hide the Navbar when user switch full screen mode by pressing F11 (Chrome).
I have tried following:
const NavbarContainer = styled.div`
height: 30px;
background-color: mediumseagreen;
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
color: white;
& :fullscreen {
display: none;
};
`
What else is needed to make it work? Currently the Navbar is still visible when I go fullscreen.
We have two options to resolve the problem with fullscreen mode.
First solution:
In styled-components you will need to use the #media all and (display-mode: fullscreen) instead of the :fullscreen pseudo-class, because this pseudo-class works only with Fullscreen API.
This will triggered with F11 key as an on/off switch, however we cann't use the Esc key to cancel the fullscreen mode.
const NavbarContainer = styled.div`
height: 30px;
background-color: mediumseagreen;
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
color: white;
#media all and (display-mode: fullscreen) {
display: none;
}
`;
Second solution:
We using Fullscreen API, when we will call this API after that the :fullscreen pseudo-class will works. This approach is good if you want to use Esc key to exit from fullscreen mode, also to assign another key (like Enter) or element (like button) to trigger fullscreen mode.
The Fullscreen API adds methods to present a specific Element (and its descendants) in fullscreen mode, and to exit fullscreen mode once it is no longer needed. This makes it possible to present desired content—such as an online game—using the user's entire screen, removing all browser user interface elements and other applications from the screen until fullscreen mode is shut off. MDN
Although, if we using #media query in our css instead of :fullscreen pseudo-class, we also can to use F11 key as a trigger. As a plus, with this approach, we will recive two different notifications about of exiting the fullscreen mode.
import { useEffect, useRef } from "react";
import styled from "styled-components";
const NavbarContainer = styled.div`
height: 30px;
background-color: mediumseagreen;
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
color: white;
#media all and (display-mode: fullscreen) {
display: none;
}
`;
const FullScreenButton = styled.button`
padding: 0.5rem 1rem;
#media all and (display-mode: fullscreen) {
display: none;
}
`;
export default function App() {
const refNavbar = useRef<HTMLDivElement>(null);
useEffect(() => {
const navbar = refNavbar.current;
const actionFn = (event: KeyboardEvent) => {
if ((event.key === 'Enter') && navbar) {
navbar.requestFullscreen();
return;
}
};
document.addEventListener('keyup', actionFn, false);
return () => {
document.removeEventListener('keyup', actionFn, false);
};
}, []);
const fullScreenOnClick = () => {
const navbar = refNavbar.current;
if (!navbar) return;
navbar.requestFullscreen();
};
return (
<div className="App">
<NavbarContainer ref={refNavbar}>Navigation</NavbarContainer>
<section>
<p>(User content!)</p>
<FullScreenButton onClick={fullScreenOnClick}>Fullscreen mode</FullScreenButton>
</section>
</div>
);
}
Caveat: Fullscreen API with assigned F11 key as trigger.
If you will try to assign the F11 key using the Fullscreen API to be able to exit also with the Esc key you will get strange behavior. It may be the cause of two different events Fullscreen API (with Esc key) and F11 key.
Fullscreen API issue with F11 and Esc buttons.
F11 key can exit programmatic-fullscreen, but programmatic-exitFullscreen cannot exit F11-fullscreen. Which is the problem you are running into. Also, Esc key cannot exit F11-fullscreen, but does exit programmatic-fullscreen. Fullscreen API not working if triggered with F11
Additionally:
Another interesting issue about :fullscreen pseudo-class. If you want to hide more than one element (like a button), this doesn't works as it will be bind to the current element. It's better to use the #media query.
Navbar and botton have a :fullscreen pseudo-class
The way it works:
const NavbarContainer = styled.div`
height: 30px;
background-color: blueviolet;
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
color: white;
#media all and (display-mode: fullscreen) {
display: none;
}
`;
The :fullscreen pseudo-class is used for a different purpose. Using javascript you can represent any DOM element to full-screen.
For example, if you will do the following thing:
document.querySelector('#navbar')?.requestFullscreen()
...then #navbar:fullscreen will work.
I think that was it
const NavbarContainer = styled.div`
height: 30px;
background-color: mediumseagreen;
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
color: white;
&::-webkit-full-screen {
display: none;
}
`

I want the arrow icon to flip up and down every time the state changes.and I want to animate it

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.

CSS Transitions not working (partly) on Safari using styled-component

I have applied css transitions all over my app and it's working flawlessly on firefox and chrome but the problem it is not working all the time on Safari for some reason sometimes the animation will choke sometimes it will partly transition sometimes works but I am not sure why
I am using explicit transition declarations not using all always like this transition: color 1s linear for example
I tried -webkit-transition even though Styled-components automatically do this but no luck too
I tried clearing cache and reload nothing changed
I am using NextJS with styled-components
transitions that are triggered on hover all working normally
any suggestions ?
one of the glitching components
import styled from "styled-components";
import { colors } from "../styles/SharedStyles";
const DarkModeToggle = styled.div`
position: fixed;
bottom: 1.25rem;
right: 1.25rem;
width: 45px;
height: 45px;
cursor: pointer;
clip-path: circle();
background-image: ${({ theme }) =>
theme.body !== colors.white
? `url("/images/misc/dark-logo.svg")`
: `url("/images/misc/light-logo.svg")`};
background-color: ${({ theme }) =>
theme.body !== colors.white ? colors.gray[700] : colors.secondary};
background-position: center;
background-repeat: no-repeat;
background-size: ${(props) => (props.theme.body !== colors.white ? `60%` : `70%`)};
transition: background 0.3s ease-in-out;
z-index: 98;
`;
export default DarkModeToggle;

Passing a custom class/style to a styled-component in Gatsby (React)

I've created the following styled-component for my gatsby project.
import React from "react"
import styled, { css } from 'styled-components'
const Button = styled.div`
background-color: #4E58F5;
width: 200px;
padding: 20px;
margin-right: 30px;
margin-top: 30px;
border-radius: 30px;
color: #FFFFFF;
transition: background-color 0.25s ease;
${props => props.primary && css`
background-color: #FFF;
color: red;
`}
`;
export default props => (
<Button>{props.buttonText}</Button>
)
I've not found the examples in the documentation to be clear or consistent enough to understand how I should be passing in the "primary" option to my components.
I'm trying to do the following, on my index.js page. The Button renders, but the primary word has no effect. What am I missing here?
<Button primary buttonText="Submit" />
The component you're exporting, does not recognize the primary property, and thus cannot pass it on to the Button element. You can fix this either by exporting the styled component itself, or by passing unrecognized props to the Button.
const Button = styled.div`
[...]
`;
export default Button;
OR
export default ({buttonText, ...props})=>(
<Button {...props}>{buttonText}</Button>
);

Resources