I am trying to render some links using ReactMarkdown in a React component, which needs some props to be passed to use special styling.
Links is my styled component which I am applying to paragraph property under renderers in ReactMarkdown. However Links to work I need to pass linkColor={linkcColor} props.
Code: Part of my code is where Container is another styled.div
<Container>
{websites.map((website, index) => (
<div key={'website' + index}>
<ReactMarkdown
source={`[${website.websiteName}](${website.externalUrl})`}
unwrapDisallowed={true}
renderers={{ paragraph: Links, link: Linkrender }}
/>
</div>
))}
</Container>
const Links = styled.div`
color: ${(p) => p.linkColor};
::before {
content: ' ';
white-space: pre;
}
::after {
content: ' /';
white-space: pre;
}
`;
Tried: The following. But it hasn't worked. It loses the source for ReactMarkdown completely and just style is applied to empty div.
<ReactMarkdown
source={`[${website.websiteName}](${website.externalUrl})`}
unwrapDisallowed={true}
renderers={{ paragraph: ({linkColor}) => (
<Links {...linkColor={linkColor}} />), <<<<<<<<<<<<< tried this
link: Linkrender }}
/>
Is the syntax correct? What could be the reason for this not to work? Is it because my styled component is a div which is getting applied on a <p>? Here Linkrender is another styled component custom made which is <a>, and that I can't change.
One way that worked was applying a styled.div as a wrapper and apply a specific styling to the paragrah in ReactMarkdown. Look at the arrows inside the code that explains what to do.
<Container>
<Heading>{`${textStripeHeading}:`}</Heading>
{websites.map((website, index) => (
<Links key={'website' + index} linkColor={linkColor}> <<<<<<<<<<< add <Links> here
<ReactMarkdown
source={`[${website.websiteName}](${website.externalUrl})`}
unwrapDisallowed={true}
renderers={{ paragraph: MarkdownStyle, link: Linkrender }} <<<<<<<<< create new style.p with style and apply to paragraph
/>
</Links>
))}
</Container>
const MarkdownStyle = styled.p`
::before {
content: ' ';
white-space: pre;
}
::after {
content: ' /';
white-space: pre;
}
`;
This is just one solution that worked. I am waiting for others to offer better solution.
Related
I have my element like this:
const Input = (props: InputProps) => (
<div className={classes.InputElement}>
<input {...props} />
</div>
)
.InputElement::after:has(input:invalid) {
content: 'Hello world';
color: red;
}
But the code doesn't behave as intended. I was wondering if this is actually even feasible?
I assume you're trying to add an after pseudo element to .InputElement if the input element has invalid, in such case: .InputElement:has(input:invalid)::after.
Otherwise you're querying for when the after pseudo element has input:invalid which it can't.
I'm trying to make the MUI component Breadcrumbs responsive:
When the Breadcrumbs component takes all of its space, its items shrink using ellipsis like any Typography component with noWrap prop set.
I know it has itemsBeforeCollapse, itemsAfterCollapse and maxItems props but these props are about item number compared to viewport size, not about each item width.
I tried to set the noWrap property to Typography and Link components (as it extends Typography props), but the ellipsis does not show up and the Link or Typography component does not shrink.
<Breadcrumbs>
{links.map((link, i) =>
i !== links.length - 1 ? (
<Link
key={i}
underline={'hover'}
noWrap
>
{link}
</Link>
) : (
<Typography noWrap key={i}>
{link}
</Typography>
),
)}
</Breadcrumbs>
You can reproduce the issue on this codeSandbox:
If I understand you correctly, the problem is that the noWrap style is not affecting the right element.
Why?
noWrap affect elements that its width limited either explicit (e.g. width: 100px) or implicit (by parent's width).
In your case, the Link and the Typography's width is not limited.
What can you do?
Breadcrumbs renders ol with display: flex. In order to force the children (li) to stand a line (and take a third in your case) you should give it flex: 1. From this point, you can give the li the ellipsis styles.
The last part, how to give the li these styles? There are some ways as described at the css section.
I would take the styled approach and this is how it looks
import * as React from "react";
import Typography from "#mui/material/Typography";
import Breadcrumbs from "#mui/material/Breadcrumbs";
import Link from "#mui/material/Link";
import { styled } from "#mui/material/styles";
const StyledBreadcrumbs = styled(Breadcrumbs)`
.MuiBreadcrumbs-li {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
`;
export default function BasicBreadcrumbs() {
const links = [
"this is a waaaaaaaaaaaaay too long link",
"and another link that can be long too",
"this is the last link and guess what? It's waaaaaaaaaaaaaaaaaaayyy more longer"
];
return (
<StyledBreadcrumbs>
{links.map((link, i) =>
i !== links.length - 1 ? (
<Link key={i} underline={"hover"}>
{link}
</Link>
) : (
<Typography key={i}>
{link}
</Typography>
)
)}
</StyledBreadcrumbs>
);
}
https://codesandbox.io/s/basicbreadcrumbs-material-demo-forked-y1bbo?file=/demo.js
I'm using Prismjs alongside Mdx for a code-related blog. I'm using it to show code blocks in a manner consistent with other blogs.
I'm running into an issue where the rendered code blocks (inside a <pre> element are too wide on my mobile layout. For now I am content to have things scroll on the horizontal axis. I'm 99% certain that the <pre> elements are what's breaking the layout because when I comment them out of the blog post, the layout works as expected.
Specifically, I'm using a package called prism-react-renderer (alongside Gatsby), and the code I have for the CodeBlock element (that handles the syntax highlighting) is more or less verbatim from the documentation for prism-react-renderer, but is included here for convenience:
import React from 'react'
import Highlight, { defaultProps } from 'prism-react-renderer'
import theme from 'prism-react-renderer/themes/nightOwl'
const CodeBlock = (props) => {
const className = props.children.props.className || ''
const matches = className.match(/language-(?<lang>.*)/)
return (
<Highlight {...defaultProps} code={props.children.props.children.trim()} language={
matches && matches.groups && matches.groups.lang
? matches.groups.lang
: ''
}
theme={theme}>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<pre className={className} style={{ ...style }}>
<code>
{tokens.map((line, i) => (
<div key={i} {...getLineProps({ line, key: i })}>
{line.map((token, key) => (
<span key={key} {...getTokenProps({ token, key })} />
))}
</div>
))}
</code>
</pre>
)}
</Highlight>
)
}
export default CodeBlock
This is the component used in the blog post template that handles rendering the .mdx files into HTML:
import React from 'react'
import { Link, graphql } from 'gatsby'
import { MDXRenderer } from 'gatsby-plugin-mdx'
import { MDXProvider } from '#mdx-js/react'
import Layout from '../components/layout'
import CodeBlock from '../components/code-block'
const components = {
pre: CodeBlock
}
const BlogPostTemplate = ({ data, pageContext, location }) => {
const post = data.mdx
const { previous, next } = pageContext
return (
<Layout>
*** Removed irrelevant component ***
<MDXProvider components={components}>
<div className='blog-post-wrapper'>
<article className='blog-post-content'>
<header>
<h1>
{post.frontmatter.title}
</h1>
<time dateTime={post.frontmatter.date}>
{post.frontmatter.date}
</time>
</header>
<MDXRenderer>{post.body}</MDXRenderer>
</article>
<footer className='blog-post-footer'>
*** Removed irrelevant components ***
</footer>
</div>
</MDXProvider>
</Layout>
)
}
export default BlogPostTemplate
I have tried a few different things: flex shrink, applying overflow-x: scroll and overflow-x: auto to both the <pre> element and its parents. When I apply a fixed width to the <pre> element and overflow-x: scroll I can get the behavior I want but I'd like to not have to use a fixed width on this if possible. The .css I have looks like this, including some obviously ineffectual styles:
.blog-post-wrapper {
display: flex;
flex-direction: column;
overflow-y: scroll;
overflow-x: scroll;
}
.blog-post-content {
flex-grow: 1;
margin-bottom: 2rem;
width: 100%;
display: flex;
flex-direction: column;
overflow-y: scroll;
overflow-x: scroll;
}
.blog-post-content .prism-code {
padding: 20px;
border: 3px solid red;
flex-shrink: 1;
overflow-y: scroll;
overflow-x: scroll;
}
I'll attach images of the way the <pre> element is rendering presently, in inspector:
And this is how it looks if I set a fixed width (in inspector):
It's probably too late, but I had the same issue and I was able to fix it by
Adding max-width css property to the main layout. The value should be equal to window.screen.width. I had to use the following hack to be able to get the screen size:
const [windowWidth, setWindowWidth] = useState(width)
useEffect(() => {
setWindowWidth(window.screen.width)
}, [])
Adding overflow: scroll to the pre in the CodeBlock
Not ideal, but I found this combination of CSS properties working together:
pre code {
display: inline-block;
width: 80vw;
overflow-x: auto;
}
I have created a Container component with component styles in next.js.
When I declare use of this container component throughout my site, I would like to add a className to it and subjectively style it depending on the context of its use.
Here is an example of my Container component:
const Container = (props) => (
<>
<div className="container">
{props.children}
</div>
<style jsx>{`
.container{
width: 100%;
max-width: 1200px;
margin: 0 auto;
}
`}</style>
</>
)
export default Container;
So, I want to give it a maximum width of 1200px, and centre it with auto margins. No problem, I have done that.
Now, I am planning to use this component in the header of my site. But in the case of the header, I would like the Container component to be a flexbox:
import Container from './Container'
const Header = (props) => (
<>
<header>
<Container className="header-col">
<div>
<h1>{props.title}</h1>
</div>
<nav>
<ul>
{/* Navigation items */}
</ul>
</nav>
</Container>
</header>
<style jsx>{`
.header-col{
display: flex;
justify-content: space-between;
}
`}</style>
</>
)
export default Header;
When I view the site, I noticed the flexbox style I specified for the Container component in the header is not present.
I was expecting the className to take effect, and allow me to add additional styles.
I believe this has something to do with it thinking that className is not a class name, rather props. But, I want to keep my code dry by creating style inheritance on components.
How could I do this?
Thanks for the help!
This is a job for styled components:
import React from "react";
import styled from "styled-components";
export default function App() {
return (
<>
<Container custom={"header"}>
<h1>Very fancy h1 with flex display</h1>
</Container>
<Container custom={"regular"}>
<h1>Non-fancy h1 with no flex display</h1>
</Container>
</>
);
}
const Container = styled.div`
display: ${(props) => (props.custom === "header" ? "flex" : "block")};
/* you can call your prop ^^^^^^ whatever you want,
just change it on the container element too*/
& h1 {
/* you can apply css to children like so */
font-family: ${(props) =>
props.custom === "header"
? '"Courier New", Courier, monospace'
: '"Arial Black", Gadget, sans-serif'};
}
`;
In the above, I've created a custom styled component that receives the custom prop and then conditionally changes the values you are looking to adjust. To make the changes more visible to the eye, I've also styled the font so you can see the immediate difference between the two <Container> elements.
For a solution that is more scalable (e.g., for different themes), use ThemeProvider:
import React from "react";
import styled, { ThemeProvider } from "styled-components";
export default function App() {
return (
<>
<ThemeProvider theme={ContainerHeader}>
<Container>
<h1>Very fancy h1 with flex display</h1>
</Container>
</ThemeProvider>
<Container theme={"regular"}>
<h1>Non-fancy h1 with no flex display</h1>
</Container>
</>
);
}
const Container = styled.div`
display: ${(props) => props.theme.display};
& h1 {
font-family: ${(props) => props.theme.h1Font};
}
`;
Container.defaultProps = {
theme: {
display: "block",
h1Font: '"Arial Black", Gadget, sans-serif'
}
};
const ContainerHeader = {
display: "flex",
h1Font: '"Courier New", Courier, monospace'
};
CodeSandbox: https://codesandbox.io/s/stack-conditional-styled-components-vmnpn?file=/src/App.js:0-773
I believe I have found the answer to my own question (I will still leave this question open for a couple more days just in case it can be improved).
In order to pass styles, you can add the "global" flag to the styled JSX and append additional classnames in the component with props.className.
Parent Container component using props.className:
const Container = (props) => (
<>
<div className={`container ${props.className}`}>
{props.children}
</div>
<style jsx>{`
.container{
width: 100%;
max-width: 1200px;
margin: 0 auto;
}
`}</style>
</>
)
export default Container;
Then, when you want to use that component, you can add additional styles with the global flag in the <style jsx>:
Container being used and styled even more in the header:
import Container from './Container';
const Header = (props) => (
<>
<header>
<Container className="header-col">
<div>
<h1>{props.title}</h1>
</div>
<nav>
<ul>
<li>Hello</li>
<li>There</li>
</ul>
</nav>
</Container>
</header>
<style jsx global>{`
.header-col{
display: flex;
justify-content: space-between;
}
`}</style>
</>
)
export default Header;
This is not 100% perfect though (but in my opinion it is still pretty good):
The global flag makes your styles global. So other components can use
these styles (I believe)
You need to make sure your components take in props.className to append additional classnames for this global style
I am a beginner to CSS-in-JS and emotion, and trying to port a sass react app to emotion. Right from the start I already have the issue of not knowing how to style the body tag.
Do people generally use document.body.style to do this? I can't find this covered anywhere ...
Suppose I want to port following code to emotion, how would that be accomplished?
$bodyFillColor: rgb(218, 236, 236);
body {
margin: 0;
padding: 0;
min-height: 100vh;
max-width: 100vw;
background-color: $bodyFillColor;
.noScroll {
overflow: hidden;
}
}
Have any best practices evolved yet that cover this?
With Emotion you can set something up, like the following create-react-app example, to inject global styles:
import React from 'react';
import ReactDOM from 'react-dom';
import { Global, css } from '#emotion/core'
const bodyFillColor = `rgb(218,236,236)`;
class App extends React.Component {
render() {
return(
<div>
<Global
styles={css`
body {
background: ${bodyFillColor};
margin: 0;
padding: 0;
min-height: '100vh';
max-width: '100vw';
}
`}
/>
<Global
styles={{
'body.noScroll': {
// Prevent scrolling; conditionally activate this
// in subcomponents when necessary ...
overflow: 'hidden',
},
}}
/>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
This shows an example of injecting a style on the body and also assigning a class to the body that can conditionally be activated later on.
eg.
{this.state.activate && <Global styles={{`stylesetc`}}/>}
https://emotion.sh/docs/globals
Alternative
StyledComponents uses a CSS-in-JS approach and works great with React applications. This is a technique I've used in the past straight from the documentation:
import { createGlobalStyle } from 'styled-components'
const GlobalStyle = createGlobalStyle`
body {
color: ${props => (props.whiteColor ? 'white' : 'black')};
}
`
// later in your app
<React.Fragment>
<Navigation /> {/* example of other top-level stuff */}
<GlobalStyle whiteColor />
</React.Fragment>
If you're using react application you can create index.css file and set your wanted properties for the body. Then you must import the index.css file in your index.js file and the changes will take place.
As per the question if the task is as small as changing body's background color in js then below approach can also be followed any where in your code most probably in App.js
if(theme==='dark')
document.body.style.background = '#000'
else
document.body.style.background = '#FFF'
No need to use a whole styling library for it.
Also i tried editing document.body.style, you can try that too according to below example
if(theme==='dark')
bgColor = '#000'
else
bgColor = '#FFF'
document.body.style= `background: ${bgColor}`
Remember following 2nd approach you may overwrite whole body style so please take care of that.
I hope this helps :)