I'm running into a problem trying to encapsulate my Modal styles with styled-components. The following approach fails to work and the defaultProps() route is a no go as well. After looking at how react-modal generates the modal elements outside of the <App/> root in the DOM, it's not surprising this is causing problems. It looks like buildClassName() is correctly adding the 'modal-overlay' className, yet even the second approach doesn't work. I suspect styled-components doesn't inject the CSS as it doesn't see any of it's children, or any of the <App/> children using it. Any ideas on how to get this to work to avoid using a global css class for the overlay?
const overlayClassName = 'modal-overlay'
const StyledModal = styled(Modal).attrs({
overlayClassName
})`
styles: here;
&.${overlayClassName} {
doesnt: work;
}
.${overlayClassName} {
also-doesnt: work;
}
`
For anyone searching, I solved this using a wrapper function, assuggested by @exogen
function ReactModalAdapter ({ className, modalClassName, ...props }) {
return (
<ReactModal
className={modalClassName}
portalClassName={className}
{...props}
/>
)
}
const StyledModal = styled(ReactModalAdapter).attrs({
overlayClassName: 'Overlay',
modalClassName: 'Modal'
})`
.Modal {
styles: here;
}
.Overlay {
styles: here;
}
`
@NathanielHill Were you getting this to work without using injectGlobal?
@christinechou I use this method, and yes, it works without any injectGlobal necessary.
The only thing you should need injectGlobal for is if you want the optional body styling like:
// Remove scroll on the body when react-modal is open.
injectGlobal`
.ReactModal__Body--open {
overflow: hidden;
}
`;
(because that class is applied to the <body>, not our modal component)
Got it - so the reason this works is that it's overwriting the React Modal's portalClassName with the className being generated by styled components and attached to the component. I had it wrong (I was passing down the same className from the adapter wrapper into the ReactModal className). Thanks!
Exactly! :)
I needed to go one step further than the example above to get full control of styles:
const StyledModal = styled(ReactModalAdapter).attrs({
overlayClassName: {
base: "Overlay",
afterOpen: "Overlay--after-open",
beforeClose: "Overlay--before-close"
},
modalClassName: {
base: "Modal",
afterOpen: "Modal--after-open",
beforeClose: "Modal--before-close"
}
})`
.Modal {
&--after-open {
}
&--before-close {
}
}
.Overlay {
&--after-open {
}
&--before-close {
}
}
`;
`
Hope this helps someone :)
I solved the problem without using .attrs().
function ReactModalAdapter ({ className, ...props }) {
const contentClassName = `${className}__content`;
const overlayClassName = `${className}__overlay`;
return (
<Modal
portalClassName={className}
className={contentClassName}
overlayClassName={overlayClassName}
{...props}
/>
)
}
const StyledModal = styled(ReactModalAdapter)`
&__overlay {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background-color: rgba(255, 255, 255, 0.75);
}
&__content {
position: absolute;
top: 40px;
left: 40px;
right: 40px;
bottom: 40px;
border: 1px solid #ccc;
background: #fff;
overflow: auto;
-webkit-overflow-scrolling: touch;
borderRadius: 4px;
outline: none;
padding: 20px;
}
`;
I elaborated on @smagch's code for TypeScript. There is probably still some room for improvement for handling the transition classes:
import React from "react";
import styled from "styled-components";
import ReactModal from "react-modal";
interface Props extends ReactModal.Props {
className?: string;
}
const ReactModalAdapter: React.SFC<Props> = ({ className, ...props }: Props) => {
const contentClassName = `${className}__content`;
const overlayClassName = `${className}__overlay`;
return (
<ReactModal
portalClassName={className}
className={contentClassName}
overlayClassName={overlayClassName}
{...props}
/>
);
};
export const StyledModal = styled(ReactModalAdapter)`
&__overlay {
…
&.ReactModal__Overlay--after-open {
…
}
&.ReactModal__Overlay--before-close {
…
}
}
&__content {
…
&.ReactModal__Content--after-open {
…
}
&.ReactModal__Content--before-close {
…
}
}
`;
And if you're like me and you want to compulsively turn everything into a HoC, here's the recompose solution:
``ts
const enhance = mapProps(({ className, ...rest }) => ({
portalClassName: className,
className:${className}__content,
overlayClassName:${className}__overlay`,
...rest
} as ReactModal.Props));
const Modal = styled(enhance(ReactModal))`
&__content {
....
}
&__overlay {
...
}
`;
Most helpful comment
I solved the problem without using
.attrs().