I am Using GatsbyJs and am trying to accomplish a active class toggle. I have a simple Component composed of a anchor tag with a span inside. I want to change some css by toggling a active class on the anchor when it is clicked. Here is my code so far, I am also using styled-components. I tried some vanilla js in the broken code section below, that obviously didn't work hence broken code.
Thanks in advance
styles
const Menu = styled.a`
position: absolute;
cursor: pointer; padding: 10px 35px 16px 0px;
span, span:before, span:after{
cursor: pointer;
border-radius: 1px;
height: 5px;
width: 35px;
background: #000000;
position: absolute;
display: block;
content: '';
transition: all 200ms ease-in-out;
}
span:before{
top: -10px;
}
span:after{
bottom: -10px;
}
.active span{
background-color: transparent;
}
.active span:before, .active span:after{
top:0;
}
.active span:before{
transform: rotate(45deg);
}
.active span:after{
transform: rotate(-45deg);
}
`
component
const TemplateWrapper = ({ children }) => (
<div>
<Wrapper>
<Menu id="nav-toggle" className="menu"><span></span></Menu>
{children()}
</Wrapper>
</div>
)
TemplateWrapper.propTypes = {
children: PropTypes.func,
}
export default TemplateWrapper
broken code
document.querySelector( "#nav-toggle" )
.addEventListener( "click", function() {
this.classList.toggle( "active" );
});
This is the html that is rendered
<a class="menu sc-bwzfXH SqHLW" id="nav-toggle"><span></span></a>
Okay, you can take a look here REACT - toggle class onclick
. There are several answers that show the way how you can achieve that, but things get messier when you want to use more than one class.
I like this package to handle the cases where you deal with styling with multiple classes based on the props or state: https://github.com/JedWatson/classnames.
I simplified the styles for the sake of clarity of this answer and assumed that the menu is a global class, you want to apply as well.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import styled from 'styled-components';
const Wrapper = styled.div``;
const Menu = styled.a`
span {
color: green;
}
&.active span {
color: blue;
}
`;
class TemplateWrapper extends Component {
state = {
isActive: false
};
handleClick = () => {
this.setState(state => ({ isActive: !state.isActive }));
};
render() {
const menuClass = classNames({
menu: true,
active: this.state.isActive
});
return (
<Wrapper>
<Menu id="nav-toggle" className={menuClass} onClick={this.handleClick}>
<span>Test</span>
</Menu>
{this.props.children}
</Wrapper>
);
}
}
TemplateWrapper.propTypes = {
children: PropTypes.node
};
TemplateWrapper.defaultProps = {
children: null
};
export default TemplateWrapper;
Notice the ampersand (&) before .active. As stated in the docs, styled components supports all of CSS plus nesting.
Ampersands (&) get replaced by our generated, unique classname for that styled component, making it easy to have complex logic.
However, you can achieve the same effect without classnames by fully utilizing styled components functionality. Take a look at this section of the documentation.
A few changes needed:
const Menu = styled.a`
span {
color: ${props => (props.active ? 'blue' : 'green')};
}
`;
The render method would look like that:
render() {
return (
<Wrapper>
<Menu id="nav-toggle" className="menu" active={this.state.isActive} onClick={this.handleClick}>
<span>Test</span>
</Menu>
{this.props.children}
</Wrapper>
);
}
You can use component state to mange this.
Code will look like:
Updated: Moved logic to Menu component as it makes more sense to place it there. Below working example
class Menu extends React.Component {
constructor(props) {
super(props);
this.menuClick = this.menuClick.bind(this);
this.state = {
menuClass: '',
}
}
menuClick(e) {
const menuClass = this.state.menuClass === '' ? 'active' : '';
this.setState({ menuClass });
}
render() {
const {children, id} = this.props;
const menuClassName = `menu sc-bwzfXH SqHLW nav-toggle ${this.state.menuClass}`;
return (<a className={menuClassName} id={id} onClick={this.menuClick}>{children}</a>);
}
}
ReactDOM.render(<Menu><span>Test link</span></Menu>, document.getElementById('menu'))
.menu {
font-weight: bolder;
color: blue;
}
.active{
color:red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="menu" />
Related
I am trying to extent react component in styled-component and trying to add custom style on extended component but unable to see the style changes that I am applying
I have created a button component in /src/newbutton.js with following code
import styled from "styled-components";
const Button = styled.button`
background: ${props => props.primary ? "palevioletred" : "white"};
color: ${props => props.primary ? "white" : "palevioletred"};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
export const NewButton = ({ className, children }) => {
return (
<Button primary>Primary</Button>
)
}
And extending and creating another button component with custom style in /src/custom-button.js with following code
import styled from "styled-components";
import { NewButton } from './button'
const ButtonWrapper = styled(NewButton)`
width: 100%;
color: red
`;
const ExtendedButton = ({ className, children }) => {
return (
<ButtonWrapper />
)
}
I have added the custom style like width: 100% & color: red but it is not applying on ExtendedButton. Infect colour and width is same as NewButton
You need to pass a className to your NewButton in order to customize it, using styled-components.
Styled components works by creating a unique className that associated with a component and its CSS.
export const NewButton = ({ className, children }) => {
return (
<Button className={className} primary>Primary</Button>
)
}
I am posting the complete working code for future reference based on #Flat Globe solution. And it is working fine as expected.
I have modified the Button component code just by adding className in /src/newbutton.js with following code
import styled from "styled-components";
const Button = styled.button`
background: ${props => props.primary ? "palevioletred" : "white"};
color: ${props => props.primary ? "white" : "palevioletred"};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
export const NewButton = ({ className, children }) => {
return (
<Button primary className={className}>Primary</Button>
)
}
I have also modified the extended-button code by just passing the className in /src/custom-button.js. check the full code below
import styled from "styled-components";
import { NewButton } from './button'
const ButtonWrapper = styled(NewButton)`
width: 100%;
color: red
`;
const ExtendedButton = ({ className, children }) => {
return (
<ButtonWrapper className="extended-button"/>
)
}
I use the npm package use-dark-mode as the name implies, it makes it possible to change the theme to light or dark, The problem is that I want to change the background-color of some blocks when changing the theme to dark, and vice versa, return the old color when I switch to light mode, for example, my block background is orange, I switch to dark mode, it turns red and when I switch to light mode, it returns old orange
App.js
import React from 'react';
import './App.css'
import Content from "./components/Content/Content";
import Dark_Mode from "./components/Dark Mode/Dark_Mode";
const App = () => {
return(
<div>
<Dark_Mode />
<Content />
</div>
);
};
export default App;
Content.jsx
import React from 'react';
import './style.css'
const Content = () => {
return (
<>
<div className={"content_container"}>
<h3>Hello from React.JS</h3>
</div>
</>
);
};
export default Content;
Dark_Mode.jsx
import React from 'react';
import useDarkMode from 'use-dark-mode';
const DarkModeToggle = () => {
const darkMode = useDarkMode(false);
return (
<div>
<button type="button" onClick={darkMode.disable}>
☀
</button>
<button type="button" onClick={darkMode.enable}>
☾
</button>
</div>
);
};
export default DarkModeToggle;
style.css
#import '../../App.css';
.content_container {
margin: auto;
width: 500px;
max-width: 100%;
background: orange;
}
.content_container h3 {
text-align: center;
}
App.css
body.light-mode {
background-color: #fff;
color: #333;
transition: background-color 0.3s ease;
}
body.dark-mode {
background-color: #1a1919;
color: #999;
}
:root {
--color-orange: orange;
}
As you can see, I have App.css when the theme changes, it changes the background of the <body>, I still have Content.jsx when switching theme I want to change the background of the block with the className content_container which is connected to style.css, In addition, you may have noticed that I tried to use global styles, but I failed. Finally, I would like to show a screenshot on the site for a clear understanding of everything.
You could give the root element a class on theme change and use css variables in root, but be class specific:
Dark_mode.jsx:
function setTheme(themeName) {
document.documentElement.classList.remove('light-theme', 'dark-theme');
document.documentElement.classList.add(themeName);
}
const DarkModeToggle = () => {
const activateDarkTheme = () => setTheme('dark-theme');
const activateLightTheme = () => setTheme('light-theme');
return (
<div>
<button type="button" onClick={activateDarkTheme}>
☀
</button>
<button type="button" onClick={activateLightTheme}>
☾
</button>
</div>
);
};
Styles:
:root, // this is used for the default theme, will be overwritten by other styles with classes because of specifity
:root.dark-theme {
--color-bg: #000;
}
:root.light-theme {
--color-bg: #fff;
}
I found a more convenient solution! although it is my fault, I was a little inattentive and did not study the documentation of this package that I use in my project, here is a simple solution
Content.jsx
import './Content.css'
import useDarkMode from 'use-dark-mode';
export default function Content () {
const { value } = useDarkMode(false);
return <div>
<div className={value ? 'Dark_Mode' : 'Light_Mode'}>
<h3>Hello from React.JS</h3>
</div>
</div>
}
Content.css
.Dark_Mode {
margin: auto;
max-width: 100%;
width: 400px;
height: 275px;
background-color: orange;
}
.Light_Mode {
margin: auto;
max-width: 100%;
width: 400px;
height: 275px;
background-color: rgb(24, 106, 199);
}
I would like to add some transition styling to my side navigation on my app. I am able to do this using normal classes however in this tutorial they use css modules and i am unsure how to do this using css modules.
I would like my nav to glide in and out, at the moment it jumps statically when the onClick function fires - toggleSideDrawer.
I have used this logic but I am not sure if it is doing anything:
className={props.toggleSideDrawer ? classes.SideDrawerOpen : classes.SideDrawer
Essentially i want that when the user clicks the toggle, the transform property switches from translateX(-100%) to translateX(0) but this is not happening.
Side nav code:
import React from "react";
import Logo from "../../Logo/Logo";
import NavigationItems from "../NavigationItems/NavigationItems";
import Backdrop from "../../UI/Backdrop/Backdrop";
import Aux from "../../../hoc/Aux";
import classes from "./SideDrawer.css";
const SideDrawer = props => {
return (
<Aux classname={classes.SideDrawer}>
<Backdrop
showBackdrop={props.showSideDrawer}
clicked={props.toggleSideDrawer}
/>
{props.showSideDrawer && (
<div
onClick={props.toggleSideDrawer}
className={
props.toggleSideDrawer ? classes.SideDrawerOpen : classes.SideDrawer
}
>
<div className={classes.Logo}>
<Logo />
</div>
<nav>
<NavigationItems />
</nav>
</div>
)}
</Aux>
);
};
export default SideDrawer;
Where the code is used in my Layout component:
import React, { useState } from "react";
import Aux from "../Aux";
import classes from "./Layout.css";
import Toolbar from "../../components/Navigation/Toolbar/Toolbar";
import SideDrawer from "../../components/Navigation/SideDrawer/SideDrawer";
const layout = props => {
const [showSideDrawer, setShowSideDrawer] = useState(false);
return (
<Aux>
<SideDrawer
showSideDrawer={showSideDrawer}
toggleSideDrawer={() => {
setShowSideDrawer(!showSideDrawer);
}}
/>
<Toolbar
onMenuClick={() => {
setShowSideDrawer(!showSideDrawer);
}}
/>
<main className={classes.mainContent}> {props.children} </main>
</Aux>
);
};
export default layout;
CSS:
.SideDrawer {
position: fixed;
width: 280px;
max-width: 70%;
height: 100%;
left: 0;
top: 0;
z-index: 200;
background-color: white;
padding: 32px 16px;
box-sizing: border-box;
transform: translateX(-100%);
}
#media (min-width: 500px) {
.SideDrawer {
display: none;
}
}
.Logo {
height: 11%;
text-align: center;
}
.SideDrawerOpen {
position: fixed;
width: 280px;
max-width: 70%;
height: 100%;
left: 0;
top: 0;
z-index: 200;
padding: 32px 16px;
box-sizing: border-box;
background-color: red;
transform: translateX(0);
transition: transform 0.3s ease-out;
}
The thing is that you need the element will has the transition rule all the time.
My suggestion is to set a static class which which will hold all the styles and addd another one only for overriding transform to make it move.
Something like that (it uses scss but it's easy to do it with css)
.SideDrawer {
position: fixed;
width: 280px;
max-width: 70%;
height: 100%;
left: 0;
top: 0;
z-index: 200;
background-color: white;
padding: 32px 16px;
box-sizing: border-box;
transition: transform .3s ease;
transform: translateX(-100%);
&.show {
transform: translateX(0);
}
}
export const App = () => {
const [showSideDrawer, setShowSideDrawer] = useState(false);
const sidebarClasses = classname([
styles.SideDrawer,
{
[styles.show]: showSideDrawer
}
]);
const ToggleSidebar = () => {
return (
<button onClick={() => setShowSideDrawer(!showSideDrawer)}>
Toggle Sidebar
</button>
);
};
return (
<Fragment>
<h1>App</h1>
<div className={sidebarClasses}>
<div>Sidebar content</div>
<ToggleSidebar />
</div>
<ToggleSidebar />
</Fragment>
);
};
https://codesandbox.io/s/xenodochial-framework-04sbe?file=/src/App.jsx
#MoshFeu helped me fix this.
The problem is that you render the drawer only when showSideDrawer so before it becomes true, the sidebar is not in the DOM yet so the transition is not affecting it.
The solution is to keep it in the DOM all the time but toggle . Open class to change the style.
There are libraries that knows to make the transition works even for elements that are not in the DOM but it's a bit more complicated.
code fix for SideDrawer.js without the conditional within the return
class SideDrawer extends Component {
render() {
let sideDrawerClass = [classes.SideDrawer];
// SideDrawer will now be an array with the side drawer classes and the open class
if (this.props.showSideDrawer) {
sideDrawerClass.push(classes.Open);
}
return (
<Aux classname={classes.SideDrawer}>
<Backdrop
showBackdrop={this.props.showSideDrawer}
clicked={this.props.toggleSideDrawer}
/>
<div
className={sideDrawerClass.join(" ")}
onClick={this.props.toggleSideDrawer}
>
<div className={classes.Logo}>
<Logo />
</div>
<nav>
<NavigationItems />
</nav>
</div>
</Aux>
);
}
}
export default SideDrawer;
I have a component that is already built out like so:
const Banner = ({ image, heading, text }) => (
<Container>
<Background src={image}>
<BannerContent>
<h1>{heading}</h1>
<h2>{text}</h2>
</BannerContent>
</Background>
</Container>
);
const BannerContent = styled.div`
h1 {
font-size: 24px;
}
h2 {
font-size: 16px;
}
`;
and I'm trying to override the styles of the h1 and h2 and add new styles like so in another component:
const PageBanner = styled(Banner)`
h1 {
font-size: 20px;
width: ...
}
h2 {
font-size: 13px;
width: ...
}
`;
However, none of that is happening. I'm assuming it's because it's nested in there? Am I able to override the styles? Or should I just build a similar component to it?
If you are styling one of your own custom components, you must make sure you use the className prop that styled components gives to the component.
const Banner = ({ image, heading, text, className }) => (
<Container className={className}>
<Background src={image}>
<BannerContent>
<h1>{heading}</h1>
<h2>{text}</h2>
</BannerContent>
</Background>
</Container>
);
const PageBanner = styled(Banner)`
h1 {
font-size: 20px;
width: ...
}
h2 {
font-size: 13px;
width: ...
}
`;
If I want a button but, only the presentational part of that, so if I do:
import styled from 'styled-components'
const Button = styled.button`
color: red;
text-align: center;
`
I'm forced to render a button tag, but what about if semantically I need an anchor?
Use the "as" polymorphic prop in v4
copy/pasta from the example in the docs:
const Component = styled.div`
color: red;
`;
render(
<Component
as="button"
onClick={() => alert('It works!')}
>
Hello World!
</Component>
)
styled-components provides withComponent that'll be useful for cases where you want to use an a different tag with a component. This is similar to #siddharthkp's answer in function, but uses the API.
Example from the documentation:
const Button = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
// We're replacing the <button> tag with an <a> tag, but reuse all the same styles
const Link = Button.withComponent('a')
// Use .withComponent together with .extend to both change the tag and use additional styles
const TomatoLink = Link.extend`
color: tomato;
border-color: tomato;
`;
render(
<div>
<Button>Normal Button</Button>
<Link>Normal Link</Link>
<TomatoLink>Tomato Link</TomatoLink>
</div>
);
You can use it with a anchor tag as well, there's nothing stopping you.
import styled from 'styled-components'
const Button = styled.a`
color: red;
text-align: center;
`
If you want to keep both, you can reuse the styles by pulling them out:
import styled from 'styled-components'
const styles = `
color: red;
text-align: center;
`
const Button = styled.button`
${styles}
`
const LinkButton = styled.a`
${styles}
`
I've asked the same question on styled-components issue tracker: https://github.com/styled-components/styled-components/issues/494
And the current "solution" that I've found is:
// agnosticStyled.js
import React from 'react'
import styled from 'styled-components'
export default styled(
({tag = 'div', children, ...props}) =>
React.createElement(tag, props, children)
)
And then when you need it:
import React from 'react'
import styled from './agnosticStyled'
const Button = styled`
color: palevioletred;
text-transform: uppercase;
`
export default Button
And finally:
import React from 'react'
import Button from './Button'
const Component = () =>
<div>
<Button>button</Button>
<Button tag="button">button</Button>
<Button tag="a" href="https://google.com">button</Button>
</div>
export default Component
Here a full functioning example: https://codesandbox.io/s/6881pjMLQ
Since we're just using JavaScript, why not use a function?
const myButtonStyle = (styled, tag) => {
return styled[tag]`
color: red;
text-align: center;
`
}
const Button = myButtonStyle(styled, 'button')
As #typeoneerror pointed out, styled-components provides WithComponent. You can use this to to create a new component based on a prop containing the tag. Piggybacking off the example, it would look like this:
const _Button = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
const Button = ({ as: tag = 'button', children, ...props }) => {
// We're replacing the <button> tag with whatever tag is assigned to the 'as' prop (renamed to 'tag' and defaulted to button), but reuse all the same styles
const Composed = _Button.withComponent(tag);
// We return the newly-created component with all its props and children
return <Composed {...props}>{children}</Composed>;
};
render(
<div>
<Button>Normal Button</Button>
<Button as='a'>Normal Link</Button>
</div>
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>