Skip to content

Commit

Permalink
Add NavLink component to simplify links in headers/footers (#10)
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
fwextensions authored Mar 18, 2024
1 parent f16151a commit 3d1cd50
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 147 deletions.
15 changes: 3 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

102 changes: 41 additions & 61 deletions packages/astro/src/components/FooterNav.astro
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---
import { Icon } from "astro-icon/components";
import NavLink from "./NavLink.astro";
const routes = [
const columns = [
[
"Resources",
[
Expand Down Expand Up @@ -31,86 +32,65 @@ const routes = [
["https://www.facebook.com/codeforsanfrancisco", "facebook", true],
["https://www.linkedin.com/company/18115347/", "linkedin", true],
["https://github.com/sfbrigade/", "github", true],
["http://c4sf.me/slack", "slack", true],
["https://c4sf.me/slack", "slack", true],
["https://www.meetup.com/sfcivictech/", "meetup", true],
],
],
] as const;
// because of annoying differences between dev and build modes, due to this
// behavior (https://github.com/withastro/astro/issues/5630), always remove
// any trailing slash so currentPage can match the pages above
const fullPath = Astro.url.pathname.replace(/\/$/, "");
const currentPage = fullPath.slice(fullPath.lastIndexOf("/") + 1);
---

<nav class="container">
<ul class="grid footer-list container">
{
routes.map(([cat, links]) => (
<li class="footer-list-item">
<h3 class="footer-list-cat-name contrast">{cat}</h3>
<ul class="footer-list-cat-list">
{links.map(([page, label, needsIcon]) => (
<li class="footer-list-cat-list-item container">
{needsIcon ? (
<span class="icon-container">
<Icon name={"fa:" + label} />{" "}
</span>
) : (
""
)}{" "}
<a
href={page}
aria-current={currentPage === page ? "page" : false}
>
{label}
</a>
</li>
))}
</ul>
</li>
))
}
</ul>
</nav>
<footer class="container">
<nav>
{columns.map(([cat, links]) => (
<div>
<h3>{cat}</h3>
<ul>
{links.map(([page, label, needsIcon]) => (
<li class="footer-list-cat-list-item ">
{needsIcon &&
// adding a class attribute to the <Icon> component works just fine
// but TS complains, so tell it to keep quiet
// @ts-ignore
<Icon name={"fa:" + label} class={"icon"} />
}
<NavLink href={page}>
{label}
</NavLink>
</li>
))}
</ul>
</div>
))}
</nav>
</footer>

<style>
a[aria-current] {
--color: var(--contrast);
cursor: default;
pointer-events: none;
}
.nav-header {
display: flex;
}
.footer-list {
/*display: flex;*/
align-items: start;
}
.footer-list-item {
flex: 1;
list-style-type: none;
vertical-align: top;
}
.footer-list-cat-name {
h3 {
font-size: 1.25rem;
margin: 0;
text-transform: uppercase;
}
.footer-list-cat-list {

ul {
--nav-element-spacing-horizontal: 0;
align-items: start;
display: flex;
flex-direction: column;
margin-left: 0;
}
.footer-list-cat-list-item {
list-style-type: none;

ul:first-of-type, ul:last-of-type {
margin-left: initial;
margin-right: initial;
}

li {
margin: 0;
padding: 0;
}

.icon-container {
.icon {
display: inline-block;
min-width: 36px;
min-width: 1.5rem;
text-align: center;
}
</style>
106 changes: 39 additions & 67 deletions packages/astro/src/components/HeaderNav.astro
Original file line number Diff line number Diff line change
@@ -1,78 +1,50 @@
---
import NavLink from "./NavLink.astro";
import NavDropdown from "./NavDropdown.astro";
const routes = [
{ label: "Wiki", page: "wiki" },
{ label: "Get Started", page: "getting-started" },
{ label: "Events", page: "events" },
{ label: "Projects", page: "projects" },
{ label: "Blog", page: "blog" },
{ label: "Donate", page: "donate" },
{ label: "Get Started", page: "getting-started" },
{ label: "Events", page: "events" },
{ label: "Projects", page: "projects" },
{ label: "Blog", page: "blog" },
{ label: "Donate", page: "donate" },
{ label: "About", page: "about",
pages: [
{ label: "Code of Conduct", page: "about/code-of-conduct" }
]
}
pages: [
{ label: "Code of Conduct", page: "about/code-of-conduct" }
]
}
];
// because of annoying differences between dev and build modes, due to this
// behavior (https://github.com/withastro/astro/issues/5630), always remove
// any trailing slash so currentPage can match the pages above
const fullPath = Astro.url.pathname.replace(/\/$/, "");
const currentPage = fullPath.slice(fullPath.lastIndexOf("/") + 1);
---

<nav class="container">
<ul>
<li>
<a href="./" class="contrast">
<strong>SF Civic Tech</strong>
</a>
</li>
</ul>
<ul>
{routes.map(route => (
<li class={`nav-item ${!!route.pages?.length ? "dropdown" : ""}`}>
<a href={route.page} aria-current={currentPage === route.page ? "page" : false}>{route.label}</a>
{!!route.pages?.length &&
<div class="dropdown-content">
{route.pages.map(page => (
<a href={page.page} aria-current={currentPage === page.page ? "page" : false}>{page.label}</a>
))}
</div>
}
</li>
))}
</ul>
</nav>
<header class="container">
<nav>
<ul>
<li>
{/* the href of "" on the homepage link here looks a little weird, but the base
path is prefixed to these links, so basePath + "" == the root page */}
<NavLink href="" class="contrast">
<strong>SF Civic Tech</strong>
</NavLink>
</li>
</ul>
<ul>
{routes.map(({ label, page, pages }) => (
<li>
<NavLink href={page}>
{label}
</NavLink>
{!!pages?.length &&
<NavDropdown items={pages} />
}
</li>
))}
</ul>
</nav>
</header>

<style>
a[aria-current] {
--color: var(--contrast);
cursor: default;
pointer-events: none;
header {
padding: 0;
}

.dropdown {
position: relative;
display: inline-block;
}

.dropdown-content {
display: none;
position: absolute;
background-color: #f9f9f9;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
}

.dropdown-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}

.dropdown:hover .dropdown-content {
display: block;
}

</style>
41 changes: 41 additions & 0 deletions packages/astro/src/components/NavDropdown.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
import NavLink from "./NavLink.astro";
interface Props {
items: { label: string; page: string }[];
}
const { items } = Astro.props;
---

<div class="dropdown-content">
{items.map(({ label, page }) => (
<NavLink href={page}>
{label}
</NavLink>
))}
</div>

<style>
.dropdown-content {
display: none;
position: absolute;
background-color: #f9f9f9;
white-space: nowrap;
padding: .5rem 1rem;
right: 0;
top: 80%;
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
z-index: 1;
}

/* set global styles on li's inside a nav, since we can't target their scoped
styles from here */
:global(nav li) {
position: relative;
}

:global(nav li):hover > .dropdown-content {
display: block;
}
</style>
32 changes: 32 additions & 0 deletions packages/astro/src/components/NavLink.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
interface Props {
href: string;
class?: string;
}
// we can destructure `class` from props, but we have to rename it so it won't
// trigger syntax errors in the JS
const { href, class: className } = Astro.props;
// remove the base URL from the beginning of the current page so we can match
// it against the href prop, which shouldn't include any base URL. because of
// annoying differences between dev and build modes, due to this behavior
// (https://github.com/withastro/astro/issues/5630), we also have to remove
// any trailing slash so currentPage can match the href.
const currentPage = Astro.url.pathname
.replace(import.meta.env.BASE_URL, "")
.replace(/\/$/, "");
const ariaCurrent = currentPage === href ? "page" : false;
---

<a href={href} class={className} aria-current={ariaCurrent}>
<slot />
</a>

<style>
a[aria-current] {
--pico-color: var(--pico-contrast);
cursor: default;
pointer-events: none;
}
</style>
4 changes: 2 additions & 2 deletions packages/astro/src/components/NewsSummaryItem.astro
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const postURL = "blog/" + slug;
aspect-ratio: 1;
background-size: cover;
background-position: center;
padding: var(--block-spacing-horizontal);
padding: var(--pico-block-spacing-horizontal);
overflow: hidden;
position: relative;
flex-direction: column;
Expand All @@ -37,7 +37,7 @@ const postURL = "blog/" + slug;
}

article h3 {
--color: white;
--pico-color: white;
font-size: 1rem;
margin-bottom: 0;
position: relative;
Expand Down
6 changes: 2 additions & 4 deletions packages/astro/src/layouts/BaseLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,5 @@ const { title } = Astro.props;
<main class="container">
<slot />
</main>
<footer>
<FooterNav />
</footer>
</Page>
<FooterNav />
</Page>
Loading

0 comments on commit 3d1cd50

Please sign in to comment.