Styled Components - Conditionally render an entire css block based on props - javascript

I understand that styles can be conditionally rendered such as:
const HelloWorldLabel= styled("div")<{ centered?: boolean }>`
display: ${({ centered }) => (centered ? "block" : "flex")};;
margin: ${({ centered }) => (centered ? "auto 0" : "unset")};
padding: ${({ centered }) => (centered ? "0 15px" : "unset")};
`;
This does not look DRY - How can I (is it possible) render an entire block of css styles based on props?
Something like:
const HelloWorldLabel= styled("div")<{ centered?: boolean }>`
if (centered) {
display: "block" ;
margin: $"auto 0";
padding: "0 15px" ;
} else {
......
}
`;

With styled-component, or any CSS-in-JS, you can conditionally render a css block:
import styled, { css } from 'styled-components';
const light = css`
background-color: white;
color: black;
`;
const dark = css`
background-color: black;
color: white;
`;
const Box = styled.div`
${({ isDark }) => (isDark ? light : dark)}
`;
Full Example:
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import styled, { css } from 'styled-components';
const light = css`
background-color: white;
border: 2px solid black;
color: black;
`;
const dark = css`
background-color: black;
color: white;
`;
const FlexBox = styled.div`
margin: 20px;
padding: 20px;
${({ isDark }) => (isDark ? light : dark)}
`;
const App = () => {
const [isDark, setIsDark] = useState(false);
const toggle = () => setIsDark(b => !b);
return (
<FlexBox isDark={isDark}>
<div>Some Text</div>
<button onClick={toggle}>Change Block</button>
</FlexBox>
);
};
ReactDOM.render(<App />, document.getElementById('root'));

A less verbose way that worked for me is
const SideMenu = styled.aside`
width: 200px;
${({ isHidden }) => isHidden && `
display: none;
`}
// another random prop you need here
${({ redBg }) => redBg && `
background-color: red;
`}
`;

You can use a function and return the css based on prop:
const HelloWorldLabel= styled("div")`
${({centered}) => {
if (centered) {
return `
display: "block" ;
margin: "auto 0";
padding: "0 15px";
`
} else {
return `// Other styles here`
}
}}
`;

The alternative is
let customCss = setCustomCss(position) => {
let positionCss = {
center: [ 'css: value;', 'css:value;'],
left: .....
right: ....
}
return return positionCss[position];
}
let HelloWorldLabel= styled('div')(customCss, {
/* css common to all */
})

Related

React Component behaving unexpectedly after implementing audio and search functions

I have trying to figure out why my component is not working as expected.
The below code creates a list of word cards and each card will play audio when clicked.
There is also a search function to filter out the cards.
However, I find that these two functions do not work together. After searching, sometimes, I could not get the audio working. The audio part works fine without any searching.
I am getting the error
Uncaught TypeError: myAudio.current is null
handlePlayAudio WordsList.js:45
So I am guessing when I type in the search, something is causing myAudio.current to become null. The trouble is, sometimes it works but sometimes it doen't!
Does anyone know what is going on and how to fix it?
WordsList.js
import React, { useState, useRef } from "react"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
// import { Link } from "gatsby"
// import slugify from "slugify"
import styled from "styled-components"
const WordsList = ({ words = [] }) => {
const [searchField, setSearchField] = useState("")
const myAudio = useRef("")
const filteredWords = words.filter(word => {
return (
word.english.toLowerCase().includes(searchField) ||
word.japanese.toLowerCase().includes(searchField) ||
word.romaji.toLowerCase().includes(searchField)
)
})
const handleSearchChange = event => {
const searchField = event.target.value.toLowerCase()
setSearchField(searchField)
}
return (
<Wrapper>
<div className="search-container">
<input
className="search-box"
type="search"
placeholder="search english, japanese, or romaji"
onChange={handleSearchChange}
/>
</div>
<div className="wrapper">
{filteredWords.map(word => {
const { id, english, japanese, romaji, image, audio } = word
const pathToImage = getImage(image)
const audioUrl = audio.file.url
const handlePlayAudio = () => {
myAudio.current.src = `http:${audioUrl}`
myAudio.current.play()
}
return (
<div
className="card"
onClick={handlePlayAudio}
onKeyDown={handlePlayAudio}
key={id}
>
<audio ref={myAudio} src={`http:${audio.file.url}`} />
{console.log(audio)}
{/*<Link key={id} to={`/${slug}`}>*/}
<GatsbyImage image={pathToImage} className="img" alt={english} />
<p>
<b>{english}</b> | {japanese} | {romaji}
</p>
</div>
)
})}
</div>
</Wrapper>
)
}
const Wrapper = styled.section`
.wrapper {
display: grid;
grid-gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
.card {
color: #333;
}
.card:hover {
opacity: 0.9;
cursor: pointer;
}
.img {
width: 100%;
}
p {
padding-top: 0.6rem;
font-size: 0.9rem;
}
.search-container {
margin: 0 0 1.6rem 0;
text-align: center;
}
input {
width: 100%;
padding: 0.3rem 0.5rem;
border: 1px solid #999;
border-radius: 0;
::placeholder {
/* Chrome, Firefox, Opera, Safari 10.1+ */
color: #bbb;
opacity: 1; /* Firefox */
}
:-ms-input-placeholder {
/* Internet Explorer 10-11 */
color: #bbb;
}
::-ms-input-placeholder {
/* Microsoft Edge */
color: #bbb;
}
text-transform: none;
}
`
export default WordsList
Thanks,
Andy
I seem to have come up with an answer.
I moved the audio tag out from inside the map
onclick get the audio url out
Use that url value to update and play the audio tag
import React, { useState, useRef } from "react"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
// import { Link } from "gatsby"
// import slugify from "slugify"
import styled from "styled-components"
const WordsList = ({ words = [] }) => {
const [searchField, setSearchField] = useState("")
const myAudio = useRef("")
const filteredWords = words.filter(word => {
return (
word.english.toLowerCase().includes(searchField) ||
word.japanese.toLowerCase().includes(searchField) ||
word.romaji.toLowerCase().includes(searchField)
)
})
const handleSearchChange = event => {
const searchField = event.target.value.toLowerCase()
setSearchField(searchField)
}
const handleAudio = url => {
myAudio.current.src = url
myAudio.current.play()
}
// const handleTest = test => console.log(test)
return (
<Wrapper>
<audio ref={myAudio} src={""} />
<div className="search-container">
<input
className="search-box"
type="search"
placeholder="search english, japanese, or romaji"
onChange={handleSearchChange}
/>
</div>
<div className="wrapper">
{filteredWords.map(word => {
const { id, english, japanese, romaji, image, audio } = word
const pathToImage = getImage(image)
const audioUrl = audio.file.url
return (
<div
className="card"
onClick={() => handleAudio(audio.file.url)}
key={id}
>
{/*<Link key={id} to={`/${slug}`}>*/}
<GatsbyImage image={pathToImage} className="img" alt={english} />
<p>
<b>{english}</b> | {japanese} | {romaji}
</p>
</div>
)
})}
</div>
</Wrapper>
)
}
const Wrapper = styled.section`
.wrapper {
display: grid;
grid-gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
.card {
color: #333;
}
.card:hover {
opacity: 0.9;
cursor: pointer;
}
.img {
width: 100%;
}
p {
padding-top: 0.6rem;
font-size: 0.9rem;
}
.search-container {
margin: 0 0 1.6rem 0;
text-align: center;
}
input {
width: 100%;
padding: 0.3rem 0.5rem;
border: 1px solid #999;
border-radius: 0;
::placeholder {
/* Chrome, Firefox, Opera, Safari 10.1+ */
color: #bbb;
opacity: 1; /* Firefox */
}
:-ms-input-placeholder {
/* Internet Explorer 10-11 */
color: #bbb;
}
::-ms-input-placeholder {
/* Microsoft Edge */
color: #bbb;
}
text-transform: none;
}
`
export default WordsList

Generic styled component Icon

I am styling my react component with styled-components. I want an icon component that can be used in different places just by changing size, colour props etc. I also want to pass icons names as props for different places. I am succeeded to change the size and colour but don't know how to pass the icon name as per requirement.
Here is my generic icon component:
import React from "react";
import { ReactSVG } from "react-svg";
import styled, { css } from "styled-components";
import { FaUserTie } from 'react-icons/fa';
const StyledSVGIcon = styled(FaUserTie)`
svg {
fill: black;
${({ size }) =>
size &&
css`
width: ${size};
height: ${size};
`}
${({ transform }) =>
transform &&
css`
transform: ${transform};
`}
path {
${({ color }) =>
color &&
css`
fill: ${color};
`}
}
}
`;
const GenIcon = props => {
return (
<StyledSVGIcon
src={`/icons/${props.name}.svg`}
color={props.color}
size={props.size}
transform={props.transform}
/>
);
};
export default GenIcon;
And I want to use it like this:
<GenIcon
name="FaUserNurse"
color="red"
size="16px"
/>
But the GenIcon component is not working. please help me where I am doing wrong. the icon could be any kind like svg or any other react icon library.
Try this out, you're close:
import React from "react";
import { ReactSVG } from "react-svg";
import styled, { css } from "styled-components";
import { FaUserTie, FaDocker } from "react-icons/fa";
const IconStyler = styled.span`
color: ${(props) => props.color};
& svg {
${(props) =>
props.small &&
css`
width: 14px !important;
height: 14px !important;
`}
${(props) =>
props.med &&
css`
width: 20px !important;
height: 20px !important;
`}
${(props) =>
props.large &&
css`
width: 28px !important;
height: 28px !important;
`}
}
`;
const Icon = ({ children, ...props }) => {
return <IconStyler {...props}>{children}</IconStyler>;
};
const GenIcon = () => {
return (
<div>
<h5>Any Icon</h5>
<div>
<Icon color="blue" small>
<FaUserTie />
</Icon>
</div>
<div>
<Icon color="orange" large>
<FaDocker />
</Icon>
</div>
</div>
);
};
export default GenIcon;
Here's a sandbox link: https://codesandbox.io/s/flamboyant-allen-60ho1?file=/src/GenIcon.js

How to redefine a style .tippy-tooltip in "react-tippy"

I want to use "react-tippy" for my project.
How can I override the styles .tippy-tooltip?
Me need to remove padding inside the tooltip.
Here is the Tool tip component I created for my use using react tippy and styled components. You can figure out from there how to customize it to your needs:
Tooltip.js
import React from 'react';
import 'tippy.js/dist/tippy.css';
import { TooltipText, StyledTippy } from './Tooltip.styled';
const Tooltip = ({ moveDown, moveRight, content, ...props }) => (
<StyledTippy
moveDown={moveDown}
moveRight={moveRight}
content={
<TooltipText small bold>
{content}
</TooltipText>
}
placement="bottom"
{...props}
>
{props.children}
</StyledTippy>
);
export default Tooltip;
Tooltip.styled.js
import styled from 'styled-components';
import Tippy from '#tippyjs/react';
export const TooltipText = styled.p`
font-weight: 500;
font-size: 10px;
line-height: 12px;
color: ${({ theme }) => theme.colors.tooltips.simpleText};
`;
export const StyledTippy = styled(Tippy)`
z-index: 5000;
margin-top: ${({ moveDown }) => (moveDown ? `${moveDown}px` : '0')};
margin-left: ${({ moveRight }) => (moveRight ? `${moveRight}px` : '0')};
background: ${({ theme }) => theme.colors.tooltips.simpleBackground};
height: 24px;
width: 110px;
display: flex;
align-items: center;
justify-content: center;
.tippy-arrow {
color: ${({ theme }) => theme.colors.tooltips.simpleBackground};
}
`;

how to apply a few styles properties depends on condition styled-components

Is it possible to apply a few styles of properties at once?
const Button = styled.div`
color: blue;
opacity: 0.6;
background-color: #ccc;
`
I need to pass active property, which will affect color, opacity, background-color. How can I apply styles for the active button at once instead of declaring conditions for each property?
const Button = styled.div`
color: ${props.active ? 'green' : 'blue'};
opacity: ${props.active ? 1 : 0.6};
background-color: : ${props.active ? 'white' : '#ccc'};
Create two classes and switch that classes according to property active
For example -
CSS
.activeClass{
color: green;
opacity: 1 ;
background-color:white;
}
.inactiveClass{
color: blue;
opacity: 0.6;
background-color: #ccc;
}
in Render
<button id="mybtn" className={this.props.active ? 'activeClass' : 'inactiveClass'} >mybutton</button>
See working example here
A common approach is a conditional rendering of CSS blocks with css API:
const first = css`
color: green;
opacity: 1;
background-color: white;
`;
const second = css`
color: blue;
opacity: 0.6;
background-color: #ccc;
`;
const Button = styled.div`
${({ active }) => (active ? first : second)}
`;
const App = () => {
const [active, trigger] = useReducer(p => !p, false);
return (
<Button active={active} onClick={() => trigger()}>
Press Me
</Button>
);
};
Or using common utilities like swithProp from styled-tools:
import styled, { css } from "styled-components";
import { switchProp, prop } from "styled-tools";
const Button = styled.button`
font-size: ${switchProp(prop("size", "medium"), {
small: prop("theme.sizes.sm", "12px"),
medium: prop("theme.sizes.md", "16px"),
large: prop("theme.sizes.lg", "20px")
}, prop("theme.sizes.md", "16px"))};
${switchProp("theme.kind", {
light: css`
color: LightBlue;
`,
dark: css`
color: DarkBlue;
`
}, css`color: black;`)}
`;
<Button size="large" theme={{ kind: "light" }} />

Create new component and inherit styles from styled-component

const Button = styled.button`
display: inline-block;
width: 300px;
background-color: black;
`
const ButtonHref = styled.a`
${Button}
`
So I have two styled-components. I want to inherit 'Button' styles but create another tag. I use react-emotion. How can I do this?
There are a few options here, using composition, styled components, or with props. The second option is probably what you want, but I've provided two other options as well.
1. Using composition
const baseButton = css`
color: white;
background-color: black;
`
const fancyButton = css`
background-color: red;
`
render() {
return (
<div>
<button css={baseButton}></button>
<button css={[baseButton, fancyButton]}></button>
</div>
)
}
The second button will have the baseButton and specialButton styles.
Or...
const baseButton = css`
color: white;
background-color: black;
`
const fancyButton = css`
${baseButton};
background-color: red;
`
render() {
return (
<div>
<button css={baseButton}></button>
<button css={fancyButton}></button>
</div>
)
}
2. Using styled components
const Button = styled.button`
color: white;
background-color: black;
`
const Fancy = styled(Button)`
background-color: red;
`
render() {
return (
<div>
<Button>Button</Button>
<Fancy>Fancy</Fancy>
</div>
)
}
This works for any component that accepts the className prop, which button does.
3. Using props
const Button = styled.button`
color: white;
background-color: ${props => props.fancy ? 'red' : 'black'};
`
render() {
return (
<div>
<Button>Button</Button>
<Button fancy>Fancy</Button>
</div>
)
)
If you just want an a that has the exact styles as your Button then you can do <Button as=“a” />
You could also add some custom css like this:
const ButtonBase = styled.button`
// some css here
`
const SmallButtonCustom = `
// some css here
`
const SmallButton = styled(ButtonBase)`
${SmallButtonCustom};
`

Categories