Add category filter to React UI

This commit is contained in:
Steve Paul
2023-12-19 00:49:07 +05:30
parent 5a30e2bed8
commit 983a456aaa
15 changed files with 427 additions and 199 deletions
-63
View File
@@ -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>
+47
View File
@@ -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);
}
+19
View File
@@ -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>
}
+6
View File
@@ -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);
}
+33
View File
@@ -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>
}
+3
View File
@@ -0,0 +1,3 @@
.nav {
box-shadow: inset 0px -1px 0px var(--border-muted);
}
+23
View File
@@ -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>
}
+18
View File
@@ -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);
}
+26
View File
@@ -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>
}
+13
View File
@@ -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} />
</>
}