How do I render conditional styles (React + Sass) - javascript

I have these classes in my scss file:
.errorNotice {
display: none;
font-size: 12px;
color: #D85B5F;
background: white;
padding: 5px;
.error & {
display: inline-block;
}
}
And in my render function,
{this.state && this.state.error ?
(
<p className={styles.errorNotice + (this.state.error ? styles.error : '')}>{this.state.error}</p>
) :
null
}
This isn't giving me the styles I want though. It's giving me a weird concatenation:
campaigns-components-WaitlistForm-styles_errorNotice-1X-Ty4campaigns-components-WaitlistForm-styles_error-JNXx2E
My goal is to display the errorNotice with the correct display attribute.
Thanks

Use string literals to combine strings. In classname you just need the name of the classes.
So with the ` you can start a string literal and inside of ${} you can run javascript code and return something:
<p className={`errorNotice ${this.state.error ? "error" : ''}`}>{this.state.error}</p>
Maybe it's also an option for you to have a look at styled-components(https://www.styled-components.com). IMHO for React projects this is really neat to use and you don't have to create special css-files anymore or take care of classnames.

Related

Implement live markdown rendering using slate.js

I'm writing a markdown text editor using slate.js. I'm trying to implement the following live-rendering effect (from Typora):
As you can see,
When I'm typing, the text is turning to bold automatically.
When I hit the space key, the four asterisks disappeared, only the text itself is visible.
When I focus the cursor back to the text, the asterisks shows up again (so I can modify them).
I've already implemented the first item thanks to the example of MarkdownPreview, here is the code of it (take from the slate repository):
import Prism from 'prismjs'
import React, { useCallback, useMemo } from 'react'
import { Slate, Editable, withReact } from 'slate-react'
import { Text, createEditor, Descendant } from 'slate'
import { withHistory } from 'slate-history'
import { css } from '#emotion/css'
// eslint-disable-next-line
;Prism.languages.markdown=Prism.languages.extend("markup",{}),Prism.languages.insertBefore("markdown","prolog",{blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},code:[{pattern:/^(?: {4}|\t).+/m,alias:"keyword"},{pattern:/``.+?``|`[^`\n]+`/,alias:"keyword"}],title:[{pattern:/\w+.*(?:\r?\n|\r)(?:==+|--+)/,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#+.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])([\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:/(^|[^\\])(\*\*|__)(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,lookbehind:!0,inside:{punctuation:/^\*\*|^__|\*\*$|__$/}},italic:{pattern:/(^|[^\\])([*_])(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,lookbehind:!0,inside:{punctuation:/^[*_]|[*_]$/}},url:{pattern:/!?\[[^\]]+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)| ?\[[^\]\n]*\])/,inside:{variable:{pattern:/(!?\[)[^\]]+(?=\]$)/,lookbehind:!0},string:{pattern:/"(?:\\.|[^"\\])*"(?=\)$)/}}}}),Prism.languages.markdown.bold.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.italic.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.bold.inside.italic=Prism.util.clone(Prism.languages.markdown.italic),Prism.languages.markdown.italic.inside.bold=Prism.util.clone(Prism.languages.markdown.bold); // prettier-ignore
const MarkdownPreviewExample = () => {
const renderLeaf = useCallback(props => <Leaf {...props} />, [])
const editor = useMemo(() => withHistory(withReact(createEditor())), [])
const decorate = useCallback(([node, path]) => {
const ranges = []
if (!Text.isText(node)) {
return ranges
}
const getLength = token => {
if (typeof token === 'string') {
return token.length
} else if (typeof token.content === 'string') {
return token.content.length
} else {
return token.content.reduce((l, t) => l + getLength(t), 0)
}
}
const tokens = Prism.tokenize(node.text, Prism.languages.markdown)
let start = 0
for (const token of tokens) {
const length = getLength(token)
const end = start + length
if (typeof token !== 'string') {
ranges.push({
[token.type]: true,
anchor: { path, offset: start },
focus: { path, offset: end },
})
}
start = end
}
return ranges
}, [])
return (
<Slate editor={editor} value={initialValue}>
<Editable
decorate={decorate}
renderLeaf={renderLeaf}
placeholder="Write some markdown..."
/>
</Slate>
)
}
const Leaf = ({ attributes, children, leaf }) => {
return (
<span
{...attributes}
className={css`
font-weight: ${leaf.bold && 'bold'};
font-style: ${leaf.italic && 'italic'};
text-decoration: ${leaf.underlined && 'underline'};
${leaf.title &&
css`
display: inline-block;
font-weight: bold;
font-size: 20px;
margin: 20px 0 10px 0;
`}
${leaf.list &&
css`
padding-left: 10px;
font-size: 20px;
line-height: 10px;
`}
${leaf.hr &&
css`
display: block;
text-align: center;
border-bottom: 2px solid #ddd;
`}
${leaf.blockquote &&
css`
display: inline-block;
border-left: 2px solid #ddd;
padding-left: 10px;
color: #aaa;
font-style: italic;
`}
${leaf.code &&
css`
font-family: monospace;
background-color: #eee;
padding: 3px;
`}
`}
>
{children}
</span>
)
}
const initialValue: Descendant[] = [
{
type: 'paragraph',
children: [
{
text:
'Slate is flexible enough to add **decorations** that can format text based on its content. For example, this editor has **Markdown** preview decorations on it, to make it _dead_ simple to make an editor with built-in Markdown previewing.',
},
],
},
{
type: 'paragraph',
children: [{ text: '## Try it out!' }],
},
{
type: 'paragraph',
children: [{ text: 'Try it out for yourself!' }],
},
]
export default MarkdownPreviewExample
My question is, how can I implement the second and third items? I've been thinking about it for a long time, but didn't find any good way to achieve them.
The good news:
The original text is saved unharmed (somewhere in your application)
Slate.js is rendering a new element for each style. thanks to the live demonstration link you've posted it is clear that the DOM is saving the state of your markdown (which suggest that we can add manipulations on-top of slate if required)
The bad news:
Implementing an editor is a headache. Using an existing one is not always as intuitive and easy as we expected. That's why I love the Monaco editor project so much (a free editable editor that gives me the experience of VS code for free? yes please!)
What we can do?
the easy way:
Use Monaco editor in our project. From my experience - it is better in terms of coding and formatting technical texts (and maybe this is the engine behind VS code)
The not-so-easy way:
We can make a simpler implementation - a panel with the raw text and another panel for preview (by the way - this is exactly what happens when you edit markdown files in VS code anyways). That way we can always render the view to reflect our most recent changes. For starters - you can use this project to do it and add tweaks to it so suit your demands
The hard way:
Using slate.js commands (read this doc for the general concept + useful examples). This approach will let slate handle the load but requires you to deep-dive into that project as well. Note that you can register custom commands and queries to suit your need without breaking your work pipeline (read this)
The insane way:
Try to override slate by using custom events on-top of rendered elements. This can be tricky but achievable if you love to play with projects internals and inject values on the fly. Not very recommended though
My Recommendation
Create a custom command that will apply the markdown style you want (for instance: bold)
Use slate handler for tracking the space key hit and use your command
function onKeyDown(event, editor, next) {
if (event.key == 'Enter') {
// TODO: add markdown style
editor.applyMarkdownBold() // applyMarkdownBold is a made-up name for your custom command
} else {
return next()
}
}
I know - this example will apply bold style to all text but - if you combine it with a range selection you will get the style you need for the relevant area in you editor (again - docs for the rescue)
From this point - applying a specific selection via key press or adding the markdown characters by clicking it (using an event + query + command) is only a matter of time

How to pass custom data-attribute to styled component?

I have this Styled component, where I'm trying to pass DATA-attribute which is coming from props to it. (This is the solution we have on Stack Overflow)
export const InlineEditReadViewErrorContainer = styled.div.attrs(props => ({
'data-cy': props.dataCy
}))`
border: 2px solid #de350b;
border-radius: 3px;
`;
This is how I use this styled component in code
<InlineEditReadViewErrorContainer dataCy='blabla'>
{readView}
</InlineEditReadViewErrorContainer>
But this is doesn't change anything
I think that we must use correctly the attributes that are added to a component in React and more if they are needed only to style a component.
We should use as many native properties as possible but without compromising the private data that would be exposed in the HTML that the client displays in broser, therefore:
If you are going to use data-attributes:
Remember how to name these attributes:
The attribute name should not contain any uppercase letters, and must be at least one character long after the prefix "data-"
Note: I would just use the simplest possible, with booleans to give a set of properties as the first answer described, for example:
component.js
<Error data-active={true}>
{readView}
</Error>
component.styles.js
export const Error = styled.div`
&[data-active="true"] {
border: 2px solid #de350b;
border-radius: 3px;
}
`;
If you want to use custom props without them being displayed in the DOM as the second comment has described, using transient props:
For sample:
component.js
<Error $active={true}>
{readView}
</Error>
component.styles.js
export const Error = styled.div`
${({$active}) => $active ? css`
border: 2px solid #de350b;
border-radius: 3px;
`: null}
`;
The prop should already be "passed" in the sense that it will show up on the component for the purposes of using it in Cypress. If you want to use it internally you could also use transient props such as this
const Comp = styled.div`
color: ${props =>
props.$draggable || 'black'};
`;
render(
<Comp $draggable="red" draggable="true">
Drag me!
</Comp>
);
It was much easier. You can pass the data attribute directly where you use the styled component and everything will be fine.
<InlineEditReadViewErrorContainer data-cy='dateInput'>
{textValue}
</InlineEditReadViewErrorContainer>
Maybe it's related to your bundler, since you should be able to pass a data attribute to a styled-component directly. However, if you're extending an existing component, be aware that you need to pass it through props. Those two situations will attach the data attribute to the final HTML:
function Text(props) {
return (
<p data-foo={props['data-foo']} className={props.className}>
{props.children}
</p>
);
}
const StyledText = styled(Text)`
color: blueviolet;
`;
const StyledHeading = styled.h1`
color: gray;
`;
export default function App() {
return (
<div>
<StyledHeading data-bar="foo">Hello StackBlitz!</StyledHeading>
<StyledText data-foo="bar">
Start editing to see some magic happen :)
</StyledText>
</div>
);
}

How to add/update scoped CSS variables via JavaScript

Is there a native API which can update the CSS variables scoped under a particular CSS class(or even any other complex CSS selector)predefined in a stylesheet? The question can be generalized for not just CSS variables but other CSS properties as well, i.e whether class specific CSS properties can be updated without targeting a specific HTML element, but by targeting the class definition itself.
Please find below the code snippets which demonstrates an example scenario. You can also find comments in the code to as to what I believe is happening/I am doing on specific lines.
var toggle = true;
function changeColor() {
document.documentElement.style.setProperty('--bg-color', toggle ? 'green' : 'red');
// this works for the "outer" div since there we receive global value(value defined in :root) of --bg-color
toggle = !toggle;
// here I want to also change the scoped value of --bg-color for "inner-primary" and "inner-secondary"
// currently I can do this by doing:
document.querySelectorAll('.inner-primary').forEach(ele => ele.style.setProperty('--bg-color', toggle ? 'blue' : 'yellow'))
document.querySelectorAll('.inner-secondary').forEach(ele => ele.style.setProperty('--bg-color', toggle ? 'yellow' : 'blue' ))
// another way I can see is: we dynamically insert a style tag, but this feels very awkward and can quickly get out of hand on multiple iterations
}
:root {
--bg-color: red;
}
body {
margin: 0;
}
.outer {
width: 100vw;
height: auto;
min-height: 100vh;
text-align: center;
background-color: var(--bg-color); /* receives value from :root */
}
.inner-primary,
.inner-secondary {
width: 100px;
height: 100px;
/* received scoped value from .inner-primary or .inner-secondary defined below*/
background-color: var(--bg-color);
border: 1px solid black;
margin: auto;
margin-bottom: 10px;
}
.inner-secondary {
--bg-color: yellow;
}
.inner-primary {
--bg-color: blue;
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Testing</title>
</head>
<body>
<div class="outer">
<div class="inner-primary"></div>
<div class="inner-secondary"></div>
<div class="inner-primary"></div>
<div class="inner-secondary"></div>
<button onclick="changeColor()">Change Color</button>
</body>
</html>
Please try running this to get a full idea of intended effect. You can click "Change Color" button at the bottom to see the effects in action.
To get the intended overriding for CSS variable --bg-color for classes inner-primary and inner-secondary, I had to use querySelectorAll with the required CSS selector(in this case just a class name) and iteratively set the CSS variable for each individual element found.
By nature of how CSS gets read by the browser, feels like the other solution to this is to dynamically insert a style element tag into the DOM, with the required CSS variable update, scoped under the required class name(or any other required selector)..
But this feels awkward and can quickly get out of hand if we don't implement some system to reuse the same style tag and not insert new ones during each toggle.
Is there any other way to do this? Any native API which can solve this without having to access individual elements or without inserting style tags dynamically..?
As suggested by A Haworth and referring Change CSS of class in Javascript? I was able update changeColor function to use CSSStyleSheet(MDN link) instead. Please find the updated function below, which uses this API:
var toggle = true;
function changeColor() {
document.documentElement.style.setProperty('--bg-color', toggle ? 'green' : 'red');
// solution using document stylesheets
const styleSheet = document.styleSheets[0]
const cssRules = Array.from(styleSheet.cssRules);
const primaryClassIndex = cssRules.findIndex(cssRule => cssRule.selectorText === '.inner-primary');
const secondaryClassIndex = cssRules.findIndex(cssRule => cssRule.selectorText === '.inner-secondary');
//update primary:
styleSheet.deleteRule(primaryClassIndex);
styleSheet.insertRule(`.inner-primary {--bg-color: ${toggle ? 'yellow' : 'blue'};}`, primaryClassIndex)
//update secondary:
styleSheet.deleteRule(secondaryClassIndex);
styleSheet.insertRule(`.inner-secondary {--bg-color: ${toggle ? 'blue' : 'yellow'};}`, secondaryClassIndex)
//toggle
toggle = !toggle;
}
This is still some concern here since it seems like we can only overwrite the entire cssRule(which may also include other CSS properties) for a particular selector, and not just one required property. But this may arguably be better than updating each individual element style or inserting style tags as mentioned in the question.
Can check the full working codepen at => https://codepen.io/yadus/pen/mdWZmXX

How to pass props to .mapped elements with styled-components?

I've been hitting my head against a wall with this one and I can't quite grasp what's the issue here.
I am pulling some data from API. It's a simple object that contains username, score and isOwner which is basically information if the username equals currently logged user. Now, if isOwner is true i want to style that li position differently.
So lets say that's how my map looks like:
const List = myData.map((el, i) => (
<li key={el.name} isowner={el.isOwner ? 1 : 0}>
{i + 1}. {el.name}
<span>{el.score}</span>
</li>
));
So every li element is generated in a styled ul component.
Now, looking at the styled component list, it looks like:
export const ScoreList = styled.ul`
width: 80%;
display: flex;
flex-direction: column;
li {
border: ${props =>
props.isowner === 1 ? '2px solid yellow' : '1px solid black'};
border-radius: 5px;
}`
for some reason it ignores the value of isowner and displays everything with the black border.
Now looking at chrome dev tools I've noticed something odd.
<li isowner="1">...</li>
<li isowner="0">...</li>
<li isowner="0">...</li>
I can see the 'prop' being put there like this, which I can't notice in any other case when I'm passing props. It looks like the logic is working well but it just doesn't see the element as a prop.
Also, I am using isowner instead of isOwner because otherwise I'm getting a following warning:
React does not recognize the `isOwner` prop on a DOM element.
If you intentionally want it to appear in the DOM as a custom attribute,
spell it as lowercase `isowner` instead.
If you accidentally passed it from a parent component, remove it from the DOM element.
I've tried different methods, instead of isOwner I was comparing el.name with username that I could yoink from redux, results were the same, I got the '1' or 'true' right where I needed it but I just couldnt pass it further.
I would be very very thankful if any of you has an idea about how to deal with this.
this might help you:
export const ScoreLi = styled.li`
border: ${props =>
props.isowner === 1 ? '2px solid yellow' : '1px solid black'};
border-radius: 5px;`
const List = myData.map((el, i) => (
<ScoreLi key={el.name} isowner={el.isOwner ? 1 : 0}>
{i + 1}. {el.name}
<span>{el.score}</span>
</ScoreLi>
));

Using jade.compile to compile a jade template that is a mixin file or has partials included

I'm trying to figure out how to do this.
Problem: I'm trying to compile a jade template for my email campaign but the file I'm trying to compile is a mixin which includes some partial.
e.g.:
controllers/user.js:
var emailTemplate = jade.compile(fs.readFileSync('./views/emails/new_user.jade', 'utf8'), { filename: './views/emails/new_user.jade'});
var template = emailTemplate({
baseUrl: res.locals.baseUrl,
confirmCode: user.confirmCode,
siteLogo: config.siteLogo,
name: user.username,
email: user.email
});
./views/emails/new_user.jade:
include ../mixins/emails
div(style='margin-bottom: 20px; border: 1px solid #ddd; padding: 20px; width: 50%; margin: 0 auto 20px;')
div(style='text-align: center; border-bottom: 1px solid #EEE; padding-bottom: 10px;')
img(src='#{siteLogo}', style='text-align: center;')
p
| Hi #{name},
p
| Please confirm your account
div(style='background-color: #179159; border-bottom: 1px solid #16814F; display: block; float: left; margin-bottom: 20px; text-align: center; width: 100%;')
mixin button('test')
./views/emails/mixins/emails.jade:
mixin button(text)
button
=text
It looks like renderFile in jade.compile only opens one at a time, unfortunately.
https://github.com/visionmedia/jade/blob/master/jade.js#L950
Is there a way that I can do what I want (which is open new_user.jade which contains mixins) or do I have to do something like:
modify the renderFile source and change it to accept an array
do an fs.readFileSync() on the file
parse each line at the top checking for include
then open each of those files
concat them into one .jade file
or something crazy?
Look at the source
exports.renderFile = function(path, options, fn){
...
var str = options.cache
? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8'))
: fs.readFileSync(path, 'utf8');
return exports.render(str, options);
};
cache is disable by default, so you should not modify renderFile
to enable cache:
renderFile('path/to/file.jade', {cache: true})
I do not know exactly your problem, but you need to separate compile-time and render-time. If you mean your jade files are changed during process, so you need to recompile them.
define mixin
mixin button(text)
button= text
To call a mixin
+button('test')

Categories

Resources