Skip to content

Commit 3d1cd50

Browse files
authored
Add NavLink component to simplify links in headers/footers (#10)
- Taking the base URL into account when checking whether the current page is the same as one of the nav links is handled inside NavLink.astro. - You can pass in a class prop to apply to the NavLink anchor. - Add NavDropdown component to simplify the creation and styling of the header dropdown menus. - Add header and footer tags inside the components. - Fix the TypeScript errors. - Adjust some of the styles to work with Pico v2.
1 parent f16151a commit 3d1cd50

9 files changed

Lines changed: 173 additions & 147 deletions

File tree

package-lock.json

Lines changed: 3 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 41 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
---
22
import { Icon } from "astro-icon/components";
3+
import NavLink from "./NavLink.astro";
34
4-
const routes = [
5+
const columns = [
56
[
67
"Resources",
78
[
@@ -31,86 +32,65 @@ const routes = [
3132
["https://www.facebook.com/codeforsanfrancisco", "facebook", true],
3233
["https://www.linkedin.com/company/18115347/", "linkedin", true],
3334
["https://github.com/sfbrigade/", "github", true],
34-
["http://c4sf.me/slack", "slack", true],
35+
["https://c4sf.me/slack", "slack", true],
3536
["https://www.meetup.com/sfcivictech/", "meetup", true],
3637
],
3738
],
3839
] as const;
39-
// because of annoying differences between dev and build modes, due to this
40-
// behavior (https://github.com/withastro/astro/issues/5630), always remove
41-
// any trailing slash so currentPage can match the pages above
42-
const fullPath = Astro.url.pathname.replace(/\/$/, "");
43-
const currentPage = fullPath.slice(fullPath.lastIndexOf("/") + 1);
4440
---
4541

46-
<nav class="container">
47-
<ul class="grid footer-list container">
48-
{
49-
routes.map(([cat, links]) => (
50-
<li class="footer-list-item">
51-
<h3 class="footer-list-cat-name contrast">{cat}</h3>
52-
<ul class="footer-list-cat-list">
53-
{links.map(([page, label, needsIcon]) => (
54-
<li class="footer-list-cat-list-item container">
55-
{needsIcon ? (
56-
<span class="icon-container">
57-
<Icon name={"fa:" + label} />{" "}
58-
</span>
59-
) : (
60-
""
61-
)}{" "}
62-
<a
63-
href={page}
64-
aria-current={currentPage === page ? "page" : false}
65-
>
66-
{label}
67-
</a>
68-
</li>
69-
))}
70-
</ul>
71-
</li>
72-
))
73-
}
74-
</ul>
75-
</nav>
42+
<footer class="container">
43+
<nav>
44+
{columns.map(([cat, links]) => (
45+
<div>
46+
<h3>{cat}</h3>
47+
<ul>
48+
{links.map(([page, label, needsIcon]) => (
49+
<li class="footer-list-cat-list-item ">
50+
{needsIcon &&
51+
// adding a class attribute to the <Icon> component works just fine
52+
// but TS complains, so tell it to keep quiet
53+
// @ts-ignore
54+
<Icon name={"fa:" + label} class={"icon"} />
55+
}
56+
<NavLink href={page}>
57+
{label}
58+
</NavLink>
59+
</li>
60+
))}
61+
</ul>
62+
</div>
63+
))}
64+
</nav>
65+
</footer>
7666

7767
<style>
78-
a[aria-current] {
79-
--color: var(--contrast);
80-
cursor: default;
81-
pointer-events: none;
82-
}
83-
.nav-header {
84-
display: flex;
85-
}
86-
.footer-list {
87-
/*display: flex;*/
88-
align-items: start;
89-
}
90-
.footer-list-item {
91-
flex: 1;
92-
list-style-type: none;
93-
vertical-align: top;
94-
}
95-
.footer-list-cat-name {
68+
h3 {
69+
font-size: 1.25rem;
9670
margin: 0;
9771
text-transform: uppercase;
9872
}
99-
.footer-list-cat-list {
73+
74+
ul {
75+
--nav-element-spacing-horizontal: 0;
10076
align-items: start;
10177
display: flex;
10278
flex-direction: column;
103-
margin-left: 0;
10479
}
105-
.footer-list-cat-list-item {
106-
list-style-type: none;
80+
81+
ul:first-of-type, ul:last-of-type {
82+
margin-left: initial;
83+
margin-right: initial;
84+
}
85+
86+
li {
10787
margin: 0;
10888
padding: 0;
10989
}
11090

111-
.icon-container {
91+
.icon {
11292
display: inline-block;
113-
min-width: 36px;
93+
min-width: 1.5rem;
11494
text-align: center;
11595
}
11696
</style>
Lines changed: 39 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,50 @@
11
---
2+
import NavLink from "./NavLink.astro";
3+
import NavDropdown from "./NavDropdown.astro";
4+
25
const routes = [
36
{ label: "Wiki", page: "wiki" },
4-
{ label: "Get Started", page: "getting-started" },
5-
{ label: "Events", page: "events" },
6-
{ label: "Projects", page: "projects" },
7-
{ label: "Blog", page: "blog" },
8-
{ label: "Donate", page: "donate" },
7+
{ label: "Get Started", page: "getting-started" },
8+
{ label: "Events", page: "events" },
9+
{ label: "Projects", page: "projects" },
10+
{ label: "Blog", page: "blog" },
11+
{ label: "Donate", page: "donate" },
912
{ label: "About", page: "about",
10-
pages: [
11-
{ label: "Code of Conduct", page: "about/code-of-conduct" }
12-
]
13-
}
13+
pages: [
14+
{ label: "Code of Conduct", page: "about/code-of-conduct" }
15+
]
16+
}
1417
];
15-
// because of annoying differences between dev and build modes, due to this
16-
// behavior (https://github.com/withastro/astro/issues/5630), always remove
17-
// any trailing slash so currentPage can match the pages above
18-
const fullPath = Astro.url.pathname.replace(/\/$/, "");
19-
const currentPage = fullPath.slice(fullPath.lastIndexOf("/") + 1);
2018
---
2119

22-
<nav class="container">
23-
<ul>
24-
<li>
25-
<a href="./" class="contrast">
26-
<strong>SF Civic Tech</strong>
27-
</a>
28-
</li>
29-
</ul>
30-
<ul>
31-
{routes.map(route => (
32-
<li class={`nav-item ${!!route.pages?.length ? "dropdown" : ""}`}>
33-
<a href={route.page} aria-current={currentPage === route.page ? "page" : false}>{route.label}</a>
34-
{!!route.pages?.length &&
35-
<div class="dropdown-content">
36-
{route.pages.map(page => (
37-
<a href={page.page} aria-current={currentPage === page.page ? "page" : false}>{page.label}</a>
38-
))}
39-
</div>
40-
}
41-
</li>
42-
))}
43-
</ul>
44-
</nav>
20+
<header class="container">
21+
<nav>
22+
<ul>
23+
<li>
24+
{/* the href of "" on the homepage link here looks a little weird, but the base
25+
path is prefixed to these links, so basePath + "" == the root page */}
26+
<NavLink href="" class="contrast">
27+
<strong>SF Civic Tech</strong>
28+
</NavLink>
29+
</li>
30+
</ul>
31+
<ul>
32+
{routes.map(({ label, page, pages }) => (
33+
<li>
34+
<NavLink href={page}>
35+
{label}
36+
</NavLink>
37+
{!!pages?.length &&
38+
<NavDropdown items={pages} />
39+
}
40+
</li>
41+
))}
42+
</ul>
43+
</nav>
44+
</header>
4545

4646
<style>
47-
a[aria-current] {
48-
--color: var(--contrast);
49-
cursor: default;
50-
pointer-events: none;
47+
header {
48+
padding: 0;
5149
}
52-
53-
.dropdown {
54-
position: relative;
55-
display: inline-block;
56-
}
57-
58-
.dropdown-content {
59-
display: none;
60-
position: absolute;
61-
background-color: #f9f9f9;
62-
min-width: 160px;
63-
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
64-
z-index: 1;
65-
}
66-
67-
.dropdown-content a {
68-
color: black;
69-
padding: 12px 16px;
70-
text-decoration: none;
71-
display: block;
72-
}
73-
74-
.dropdown:hover .dropdown-content {
75-
display: block;
76-
}
77-
7850
</style>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
import NavLink from "./NavLink.astro";
3+
4+
interface Props {
5+
items: { label: string; page: string }[];
6+
}
7+
8+
const { items } = Astro.props;
9+
---
10+
11+
<div class="dropdown-content">
12+
{items.map(({ label, page }) => (
13+
<NavLink href={page}>
14+
{label}
15+
</NavLink>
16+
))}
17+
</div>
18+
19+
<style>
20+
.dropdown-content {
21+
display: none;
22+
position: absolute;
23+
background-color: #f9f9f9;
24+
white-space: nowrap;
25+
padding: .5rem 1rem;
26+
right: 0;
27+
top: 80%;
28+
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
29+
z-index: 1;
30+
}
31+
32+
/* set global styles on li's inside a nav, since we can't target their scoped
33+
styles from here */
34+
:global(nav li) {
35+
position: relative;
36+
}
37+
38+
:global(nav li):hover > .dropdown-content {
39+
display: block;
40+
}
41+
</style>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
interface Props {
3+
href: string;
4+
class?: string;
5+
}
6+
7+
// we can destructure `class` from props, but we have to rename it so it won't
8+
// trigger syntax errors in the JS
9+
const { href, class: className } = Astro.props;
10+
11+
// remove the base URL from the beginning of the current page so we can match
12+
// it against the href prop, which shouldn't include any base URL. because of
13+
// annoying differences between dev and build modes, due to this behavior
14+
// (https://github.com/withastro/astro/issues/5630), we also have to remove
15+
// any trailing slash so currentPage can match the href.
16+
const currentPage = Astro.url.pathname
17+
.replace(import.meta.env.BASE_URL, "")
18+
.replace(/\/$/, "");
19+
const ariaCurrent = currentPage === href ? "page" : false;
20+
---
21+
22+
<a href={href} class={className} aria-current={ariaCurrent}>
23+
<slot />
24+
</a>
25+
26+
<style>
27+
a[aria-current] {
28+
--pico-color: var(--pico-contrast);
29+
cursor: default;
30+
pointer-events: none;
31+
}
32+
</style>

packages/astro/src/components/NewsSummaryItem.astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const postURL = "blog/" + slug;
2424
aspect-ratio: 1;
2525
background-size: cover;
2626
background-position: center;
27-
padding: var(--block-spacing-horizontal);
27+
padding: var(--pico-block-spacing-horizontal);
2828
overflow: hidden;
2929
position: relative;
3030
flex-direction: column;
@@ -37,7 +37,7 @@ const postURL = "blog/" + slug;
3737
}
3838

3939
article h3 {
40-
--color: white;
40+
--pico-color: white;
4141
font-size: 1rem;
4242
margin-bottom: 0;
4343
position: relative;

packages/astro/src/layouts/BaseLayout.astro

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,5 @@ const { title } = Astro.props;
1515
<main class="container">
1616
<slot />
1717
</main>
18-
<footer>
19-
<FooterNav />
20-
</footer>
21-
</Page>
18+
<FooterNav />
19+
</Page>

0 commit comments

Comments
 (0)