Add category filter to React UI
This commit is contained in:
@@ -1,63 +0,0 @@
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
body: string;
|
||||
tag: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
const { href, title, body, tag } = Astro.props;
|
||||
---
|
||||
|
||||
<li class="link-card">
|
||||
<a href={href}>
|
||||
<h6 class="nu-c-h6 nu-u-mt-1 nu-u-mb-1">
|
||||
{title}
|
||||
</h6>
|
||||
<p class="nu-c-fs-small nu-u-mt-1 nu-u-mb-1">
|
||||
{body}
|
||||
</p>
|
||||
<p class="distribution">
|
||||
<span class="tag">{tag}</span>
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
<style>
|
||||
.link-card {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
background-color: var(--background-secondary);
|
||||
background-position: 100%;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--spacing-03);
|
||||
height: 136px;
|
||||
position: relative;
|
||||
box-shadow: 0px 2px 4px 0px rgba(22, 22, 22, 0.08), 0px 8px 12px -8px rgba(22, 22, 22, 0.08);
|
||||
}
|
||||
.link-card > a {
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
padding: 0.8rem 1.2rem;
|
||||
color: var(--support-info);
|
||||
}
|
||||
p {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.link-card:is(:hover, :focus-within) {
|
||||
background-position: 0;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.link-card:is(:hover, :focus-within) h6 {}
|
||||
p.distribution {margin: var(--spacing-00); position: absolute; bottom: var(--spacing-06);}
|
||||
span.tag {
|
||||
background-color: var(--background-selected);
|
||||
padding: 2px 8px;
|
||||
border-radius: 24px;
|
||||
display: inline-block;
|
||||
color: var(--text-primary-alt);
|
||||
margin-top: var(--spacing-08);
|
||||
font-size: var(--desktop-caption);
|
||||
line-height: var(--lh-desktop-caption);
|
||||
font-family: var(--system-ui);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,47 @@
|
||||
.link-card {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
background-color: var(--background-secondary);
|
||||
background-position: 100%;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--spacing-03);
|
||||
height: 136px;
|
||||
position: relative;
|
||||
box-shadow: 0px 2px 4px 0px rgba(22, 22, 22, 0.08), 0px 8px 12px -8px rgba(22, 22, 22, 0.08);
|
||||
}
|
||||
|
||||
.link-card>a {
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
padding: 0.8rem 1.2rem;
|
||||
color: var(--support-info);
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) {
|
||||
background-position: 0;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) h6 {}
|
||||
|
||||
p.distribution {
|
||||
margin: var(--spacing-00);
|
||||
position: absolute;
|
||||
bottom: var(--spacing-06);
|
||||
}
|
||||
|
||||
span.tag {
|
||||
background-color: var(--background-selected);
|
||||
padding: 2px 8px;
|
||||
border-radius: 24px;
|
||||
display: inline-block;
|
||||
color: var(--text-primary-alt);
|
||||
margin-top: var(--spacing-08);
|
||||
font-size: var(--desktop-caption);
|
||||
line-height: var(--lh-desktop-caption);
|
||||
font-family: var(--system-ui);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import "./Card.css";
|
||||
|
||||
export default function Card(props) {
|
||||
const {href, title, body, tag} = props;
|
||||
|
||||
return <li className="link-card">
|
||||
<a href={href}>
|
||||
<h6 className="nu-c-h6 nu-u-mt-1 nu-u-mb-1">
|
||||
{title}
|
||||
</h6>
|
||||
<p className="nu-c-fs-small nu-u-mt-1 nu-u-mb-1">
|
||||
{body}
|
||||
</p>
|
||||
<p className="distribution">
|
||||
<span className="tag">{tag}</span>
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
.link-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr));
|
||||
gap: var(--spacing-05);
|
||||
padding: var(--spacing-00);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import Card from "./Card";
|
||||
import "./CardsContainer.css";
|
||||
|
||||
import data from "../data/tools.json"
|
||||
|
||||
export default function CardsContainer(props) {
|
||||
const { filter } = props;
|
||||
|
||||
return <section>
|
||||
<ul role="list" className="link-card-grid">
|
||||
{data.tools
|
||||
.filter(item => {
|
||||
if (filter === "all" || filter === item.category) {
|
||||
return item;
|
||||
}
|
||||
})
|
||||
.flatMap(item => item.content)
|
||||
.sort((a, b) => {
|
||||
return a.title < b.title ? -1 : 1;
|
||||
})
|
||||
.map(({url, title, body, tag}, i) => {
|
||||
return <Card
|
||||
key={i}
|
||||
href={url}
|
||||
title={title}
|
||||
body={body}
|
||||
tag={tag}
|
||||
/>
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.nav {
|
||||
box-shadow: inset 0px -1px 0px var(--border-muted);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import "./CategoryNav.css";
|
||||
import data from "../data/tools.json";
|
||||
|
||||
import CategoryNavItem from "./CategoryNavItem";
|
||||
|
||||
export default function CategoryNav({ filter, setFilter }) {
|
||||
const navItems = [
|
||||
{title: "🔥 All", category: "all"},
|
||||
...data.tools
|
||||
];
|
||||
|
||||
return <nav className="nav">
|
||||
{navItems.map((c, i) => {
|
||||
return <CategoryNavItem
|
||||
key={i}
|
||||
title={c.title}
|
||||
category={c.category}
|
||||
filter={filter}
|
||||
setFilter={setFilter}
|
||||
/>
|
||||
})}
|
||||
</nav>
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
.nav__item {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav__item:hover {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.nav__item.is-active {
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
border-bottom: 2px solid var(--icon-primary);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import "./CategoryNavItem.css";
|
||||
|
||||
export default function CategoryNavItem(props) {
|
||||
const { title, category, filter, setFilter } = props;
|
||||
const [isActive, setIsActive] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let subscription = true;
|
||||
|
||||
if (filter === category) {
|
||||
setIsActive(true);
|
||||
} else {
|
||||
setIsActive(false);
|
||||
}
|
||||
|
||||
return (() => (subscription = !subscription));
|
||||
}, [filter]);
|
||||
|
||||
return <button
|
||||
onClick={() => setFilter(category)}
|
||||
className={`nav__item nu-u-text--secondary-alt nu-c-fs-normal nu-u-py-5 nu-u-px-0 nu-u-me-8 nav__item--filter ${isActive ? 'is-active' : ''}`}
|
||||
dangerouslySetInnerHTML={{__html: title}}
|
||||
>
|
||||
</button>
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useState } from "react";
|
||||
import CategoryNav from "./CategoryNav";
|
||||
import CardsContainer from "./CardsContainer";
|
||||
|
||||
export default function Dashboard() {
|
||||
|
||||
const [filter, setFilter] = useState("all");
|
||||
|
||||
return <>
|
||||
<CategoryNav filter={filter} setFilter={setFilter} />
|
||||
<CardsContainer filter={filter} />
|
||||
</>
|
||||
}
|
||||
Reference in New Issue
Block a user