I have a question about styling an anchor component when it is on the active page.
Here is my code:
import Link from 'next/link';
import styled from 'styled-components';
const NavStyle = styled.nav`
display: flex;
justify-content: space-between;
.nav-link a {
text-decoration: none;
color: white;
padding: 10px;
background-color: #FFCF00;
}
`;
export default function Nav() {
return (
<NavStyle>
<div className="nav-link">
<Link href="/" passHref>
<a>HOME</a>
</Link>
<Link href="/pricing">
<a>PRICING</a>
</Link>
<Link href="/terms">
<a>TERMS</a>
</Link>
<Link href="/login">
<a>LOGIN</a>
</Link>
</div>
<Link href="/">
<a>LOGO</a>
</Link>
</NavStyle>
)
}
What I want is, when the I click on the link and move to another page, the active link (that's matched with the URL) would have a green background. I have tried this, but it doesn't make any change:
const NavStyle = styled.nav`
display: flex;
justify-content: space-between;
.nav-link a {
text-decoration: none;
color: white;
padding: 10px;
background-color: #FFCF00;
&[aria-current] {
background-color: green;
}
}
`;
Next.js won't add aria-current to your active link; however, you can create a custom Link component that checks if the current pathname is the same as the href prop.
import React from "react";
import Link from "next/link";
import { useRouter } from "next/router";
const NavLink = ({ children, href }) => {
const child = React.Children.only(children);
const router = useRouter();
return (
<Link href={href}>
{React.cloneElement(child, {
"aria-current": router.pathname === href ? "page" : null
})}
</Link>
);
};
export default NavLink;
Then, you can use this component instead of the default Link whenever you want to add aria-current to the active link:
const NavStyle = styled.nav`
display: flex;
justify-content: space-between;
a {
background-color: #353637;
color: #fff;
padding: 1rem;
text-decoration: none;
&[aria-current] {
background-color: #faf9f4;
color: #353637;
}
}
`;
export default function Nav() {
return (
<NavStyle>
<div className="nav-link">
<NavLink href="/">
<a>HOME</a>
</NavLink>
<NavLink href="/pricing">
<a>PRICING</a>
</NavLink>
<NavLink href="/terms">
<a>TERMS</a>
</NavLink>
<NavLink href="/login">
<a>LOGIN</a>
</NavLink>
</div>
<Link href="/">
<a>LOGO</a>
</Link>
</NavStyle>
);
}
The best answer to this question that I have found is at https://amanexplains.com/using-aria-current-for-nav-links/
Note that the first answer to this question says to use pathname, but the link I provided describes why that won't work in some cases (slugged paths, for instance). Instead, use asPath.
Note also that this currently (NextJS 13) only works for client-side components as far as I can tell.
Related
I am using react styled components and want to create a toggle functionality or the Link components.
The first step I took was to create the useMediaQueries hook and use min-width and max-width to delimit the pixels.
import { useMediaQuery } from 'react-responsive'
// Media queries
const isDesktop = useMediaQuery({ query: '(min-width: 700px)' });
const isMobile = useMediaQuery({ query: '(max-width: 700px)' });
After this, I created the styled components and moved them into separate file called Header.styles.js:
import { Link } from "react-router-dom";
import styled from "styled-components";
export const LinkStyled = styled(Link)`
text-decoration: none;
color: #333;
margin: 0px 15px 0px 15px;
font-weight: 500;
`;
export const LinkWrapper = styled.div`
display: flex;
flex-wrap: wrap;
`;
export const HeaderWrapper = styled.header`
display: flex;
align-items: center;
justify-content: space-between;
padding: 0px 30px 0px 30px;
/* flex-wrap: wrap; */
`;
The header use the media queries hook and display/hide the menu button and the header wrapper:
// Toggle state
const [ isVisible, setIsvisible ] = useState(false);
return (
<HeaderWrapper>
<p>Expenses</p>
{isDesktop && <LinkWrapper isMobileVersion={isMobile}>
<LinkStyled to='/'>Home</LinkStyled>
<LinkStyled to='/about'>About</LinkStyled>
<LinkStyled to='/profile'>Profile</LinkStyled>
<LinkStyled to='/signup'>Signup</LinkStyled>
<LinkStyled to='/login'>Login</LinkStyled>
</LinkWrapper>}
{ isMobile && <GiHamburgerMenu size={25} onClick={() => setIsvisible(!isVisible)} /> }
</HeaderWrapper>
)
}
I am new to styled components. How can I toggle the classes or assign a class (created in the styled components file) only when the display is set on mobile?
Thanks
I don't see where exactly you wanna use it, but basically you can use ternary operator.
<LinkWrapper style={{ color: isMobile ? "#333" : "#444" }} />
This code is just an example how to solve your problem
Styled components are not just styles, but whole customized components.
I'm trying to use use-local-storage to achieve a theme changer in React.
App component:
import './App.css';
import React from 'react';
import { Navbar, SearchBar, Header, Main, Chart, Map } from './components';
import { Routes, Route, BrowserRouter } from 'react-router-dom';
import useLocalStorage from 'use-local-storage';
function App() {
// a function that toggles between darkmode and lightmode in css
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const [theme, setTheme] = useLocalStorage('theme', defaultDark ? 'dark' : 'light');
const switchTheme = () => {
const newTheme = theme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
}
console.log(theme)
return (
<BrowserRouter>
<div className='App' data-theme={theme} >
<Header />
<SearchBar />
<Navbar switchTheme={switchTheme} />
<Routes>
<Route path="/" element={<Main />} />
<Route path="/map" element={<Map />} />
<Route path="/chart" element={<Chart />} />
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
Navbar component:
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faMapLocationDot, faChartLine, faHouseUser } from '#fortawesome/free-solid-svg-icons'
import React from 'react'
import { Link } from 'react-router-dom'
const Navbar = ({switchTheme}) => {
return (
<nav className='nav'>
<button onClick={switchTheme}>Toggle</button>
<Link to='/'>
<FontAwesomeIcon icon={faHouseUser} size='4x' color='blue' />
<br></br>
Home
</Link>
<Link to='/map'>
<FontAwesomeIcon icon={faMapLocationDot} size='4x' />
<br></br>
Map</Link>
<Link to='/chart'>
<FontAwesomeIcon icon={faChartLine} size='4x' color='red' />
<br></br>
Chart</Link>
</nav>
)
}
export default Navbar
CSS:
*, *::after, *::before {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/****************** VARIABLES ******************/
:root {
--background-color:coral;
}
[data-theme="light"] {
--background-color:red;
}
[data-theme="dark"] {
--background-color:yellow;
}
body {
background-color:var(--background-color);
font-family: 'Roboto', sans-serif;
font-size: 16px;
color: #333;
line-height: 1.5;
margin: 2vmin;
}
.App {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/********************** SearchBar **********************/
form {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
max-width: 500px;
margin: 0 auto;
}
form > svg {
margin-left: -20px;
}
input {
font-size:inherit;
border-radius: 1vmin;
border: .5px solid #ccc;
padding: .5rem;
}
input:focus {
border-color: #333;
}
nav {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
background-color: yellow;
border-bottom: 1px solid #eaeaea;
width: 10vw;
height: 50vh;
border: 3px dotted purple;
align-self: flex-start;
}
a {
text-decoration: none;
}
/* a:active {
/* do sth with selected Link
} */
I am getting the correct values from console.log(theme) in App.js but I can't change the background colour of the whole app.
Any ideas to solve this issue ?
You are having a cascading issue, as you are setting your theme on body, and trying to change it later through the App component. Add the data-theme on the body itself or on html, which comes before, not on something that comes after.
Adding this useEffect in App.js just before your return would work:
useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
}, [theme]);
Find the full example as well as a CodeSandbox below:
import './App.css';
import React, {useEffect} from 'react';
import { Navbar, SearchBar, Header, Main, Chart, Map } from './components';
import { Routes, Route, BrowserRouter } from 'react-router-dom';
import useLocalStorage from 'use-local-storage';
function App() {
// a function that toggles between darkmode and lightmode in css
const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const [theme, setTheme] = useLocalStorage('theme', defaultDark ? 'dark' : 'light');
const switchTheme = () => {
const newTheme = theme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
}
useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
}, [theme]);
return (
<BrowserRouter>
<div className='App'>
<Header />
<SearchBar />
<Navbar switchTheme={switchTheme} />
<Routes>
<Route path="/" element={<Main />} />
<Route path="/map" element={<Map />} />
<Route path="/chart" element={<Chart />} />
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
NavStyles.js
import styled from 'styled-components';
export const Nav = styled.navwidth: 100%; ;
export const NavMenuMobile = styled.ul`
height: 80px;
.navbar_list_class {
font-size: 2rem;
background-color: red;
}
${props => props.navbar_list_props && `
font-size: 2rem;
background-color: gray;
`}
`;
Navbar.js
import React from 'react'
import {Nav, NavMenuMobile} from "./NavStyles";
const Navbar = () => {
return (
<Nav>
{/* work no problem */}
<NavMenuMobile navbar_list_props>Nav Bar props</NavMenuMobile>
{/* not work How to use..? */}
<NavMenuMobile className="navbar_list_class">Nav Bar class</NavMenuMobile>
</Nav>
)
}
export default Navbar
<Nav>
<NavMenuMobile className={navbar_list_props}>Nav Bar props</NavMenuMobile>
</Nav>
Try This
Looks like you are setting styles for the children within NavMenuMobile with the class "navbar_list_class".
Should work with &.navbar_list_class
export const NavMenuMobile = styled.ul`
height: 80px;
&.navbar_list_class {
font-size: 2rem;
background-color: red;
}
`;
When site is loading, page starts from body and I have to scroll up to see styled header.
(But this problem does not appear on all browsers)
I want to start scrolling with the Header.
Here is the live site: http://pavlorishko228-001-site1.btempurl.com/
Code:
import React from "react";
import { Link } from 'react-router-dom';
import styled from "styled-components";
import ScrollHandler from "../../components/ScrollHandler";
import Logo from '../../public/SmokeyWayLogo.svg';
const StyledLogo = styled("img")<{isScrolled: boolean}>`
filter: ${props => props.isScrolled ? "invert(1)" : "drop-shadow(2px 4px 3px black)"};
height: 80px;
padding-left: 0;
float: left;
`;
const StyledLink = styled("div")<{isScrolled: boolean}>`
padding: 20px;
margin: 10px;
display: inline-block;
border-radius: 5px;
&:hover {
box-shadow: 0px 0px 15px 2px ${props => props.isScrolled ? "white" : "black"};
color: black;
}
a {
text-decoration: inherit;
color: ${props => props.isScrolled ? "white" : "black"};
}
`;
const StyledNav = styled("div")<{isScrolled: boolean}>`
position: fixed;
width: 100%;
background-color: ${props => props.isScrolled ? "transparent " : "white"};
`;
function Header(){
const _isScrolled = ScrollHandler();
return(
<header>
<StyledNav isScrolled={_isScrolled}>
<StyledLogo isScrolled={_isScrolled} src={Logo}></StyledLogo>
<StyledLink isScrolled={_isScrolled}>
<Link to="./">Smokey Way</Link>
</StyledLink>
<StyledLink isScrolled={_isScrolled}>
<Link to="./">Головна</Link>
</StyledLink>
<StyledLink isScrolled={_isScrolled}>
<Link to="./">Меню</Link>
</StyledLink>
</StyledNav>
</header>
)
}
export default Header;
I used chrome on my device and was fine, I mean i saw your header after loading the page.
Your problem is not normal and its kinda weird for me, But i suggest you JS (in this code before loading page it makes you to be sure that you are at the top of the current page) and give me the feedback:
$(window).on('beforeunload', function(){
$(window).scrollTop(0);
});
Using Nextjs with styled-jsx I have written the component below.
Now I would like to know how to apply styled-jsx to the jsx that is being returned from the getLinks method.
In the below example the jsx coming from the getLinks method are not being styled.
class MainHeader extends Component {
getLinks = () => {
const links = linkData.map(link => (
<Link key={link.path} href={link.path}>
<a className="link">{link.title}</a>
</Link>
));
return links;
};
render() {
return (
<div className="mainContainer">
{this.getLinks()}
<style jsx>
{`
.mainContainer {
background: ${colors.pri};
display: flex;
height: 60px;
}
.link {
color: ${colors.tPri};
font-size: 2rem;
margin-left: 1.6rem;
}
`}
</style>
</div>
);
}
}
I know I could do it like I have done in the code below, but I'd rather have my render method a bit more clean.
lass MainHeader extends Component {
render() {
return (
<div className="mainContainer">
{linkData.map(link => (
<Link key={link.path} href={link.path}>
<a className="link">{link.title}</a>
</Link>
))}
<style jsx>
{`
.mainContainer {
align-items: center;
background: ${colors.pri};
display: flex;
height: 60px;
}
.link {
color: ${colors.tPri};
font-size: 2rem;
margin-left: 1.6rem;
text-decoration: none;
}
`}
</style>
</div>
);
}
}
the author of styled-jsx here :)
You have two options. The first being moving the .link styles to where they belong (the getLinks method):
getLinks = () => {
const links = linkData.map(link => (
<>
<Link key={link.path} href={link.path}>
<a className="link">{link.title}</a>
</Link>
<style jsx>{`
.link {
color: ${colors.tPriD};
}
`}</style>
</>
));
return links;
}
Don't worry about the loop, styled-jsx will dedupe them and render the styles only once.
The second option is using a newer feature that is css.resolve:
getLinks = (scopedClass) => {
const links = linkData.map(link => (
<Link key={link.path} href={link.path}>
<a className={`${scopedClass} link`}>{link.title}</a>
</Link>
));
return links;
}
render() {
const { className, styles } = css.resolve`
.link {
color: ${colors.tPri};
font-size: 2rem;
margin-left: 1.6rem;
}
`
return (
<div className="mainContainer">
{styles}
{this.getLinks(className)}
</div>
)
}
If you are not using props in the styles I recommend you to move them outside of the component as it will perform better.
Hope this helps.
FWIW we have a community on Spectrum for styled-jsx https://spectrum.chat/styled-jsx
Maybe something like this will be clean enough for you
class MainHeader extends Component {
getLinks = () => {
const links = linkData.map(link => (
<Link key={link.path} href={link.path}>
<a className="link">{link.title}</a>
</Link>
));
return <>
{links}
<style jsx>
{`
.link {
color: ${colors.tPri};
font-size: 2rem;
margin-left: 1.6rem;
}
`}
</style>
</>;
};
render() {
return (
<div className="mainContainer">
{this.getLinks()}
<style jsx>
{`
.mainContainer {
background: ${colors.pri};
display: flex;
height: 60px;
}
`}
</style>
</div>
);
}
}