Merge branch 'main' into about-page

This commit is contained in:
Kalidou Diagne
2023-09-14 21:36:31 +02:00
29 changed files with 554 additions and 65 deletions

57
app/not-found.tsx Normal file
View File

@@ -0,0 +1,57 @@
import "@/styles/globals.css"
import React from "react"
import Image from "next/image"
import Link from "next/link"
import { fontDisplay, fontSans } from "@/lib/fonts"
import { Button } from "@/components/ui/button"
import { SiteHeader } from "@/components/site-header"
import { TailwindIndicator } from "@/components/tailwind-indicator"
export default function NotFound() {
return (
<html
lang="en"
className={`${fontSans.variable} ${fontDisplay.variable}`}
suppressHydrationWarning
>
<head />
<body className="min-h-screen">
<div className="relative flex flex-col h-screen bg-anakiwa-50">
<SiteHeader />
<div className="container m-auto">
<div className="flex flex-col -mt-16 gap-7">
<div className="flex flex-col items-center justify-center gap-3 text-center">
<div className="flex flex-col gap-2">
<Image
width={80}
height={80}
src="/icons/404-search.svg"
alt="emotion sad"
className="w-12 h-12 mx-auto md:w-24 md:h-24 text-anakiwa-400"
/>
<span className="text-5xl font-bold text-anakiwa-400 md:text-8xl font-display">
404
</span>
</div>
<div className="flex flex-col gap-5">
<span className="text-2xl font-bold md:text-6xl text-tuatara-950 font-display">
Oops! Page not found
</span>
<span className="font-sans text-base font-normal md:text-lg">
The page you are looking for might have been removed, had
its name changed or is temporarily unavailable.
</span>
</div>
</div>
<Link href="/" className="mx-auto">
<Button variant="black">Go to homepage</Button>
</Link>
</div>
</div>
</div>
<TailwindIndicator />
</body>
</html>
)
}

View File

@@ -143,6 +143,21 @@ export const Icons = {
/> />
</svg> </svg>
), ),
arrowDown: (props: LucideProps) => (
<svg
width="25"
height="25"
viewBox="0 0 25 25"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M12.4997 13.6719L17.4497 8.72192L18.8637 10.1359L12.4997 16.4999L6.13574 10.1359L7.54974 8.72192L12.4997 13.6719Z"
fill="currentColor"
/>
</svg>
),
plus: (props: LucideProps) => ( plus: (props: LucideProps) => (
<svg <svg
width="35" width="35"

View File

@@ -2,61 +2,79 @@
import { import {
ProjectFilter, ProjectFilter,
ProjectSortBy,
useProjectFiltersState, useProjectFiltersState,
} from "@/state/useProjectFiltersState" } from "@/state/useProjectFiltersState"
import { CategoryTag } from "../ui/categoryTag" import { CategoryTag } from "../ui/categoryTag"
import { Dropdown } from "../ui/dropdown"
const labelClass = "h-5 text-xs text-base md:h-6 text-slate-900/70 md:text-lg" const labelClass = "h-5 text-xs text-base md:h-6 text-slate-900/70 md:text-lg"
const projectSortItems: { label: string; value: ProjectSortBy }[] = [
{ label: "Random", value: "random" },
{ label: "Title: A-Z", value: "asc" },
{ label: "Title: Z-A", value: "desc" },
{ label: "Relevance", value: "relevance" },
]
const getSortLabel = (sortBy: ProjectSortBy) => {
return projectSortItems.find((item) => item.value === sortBy)?.label || sortBy
}
export const ProjectResultBar = () => { export const ProjectResultBar = () => {
const { activeFilters, toggleFilter, projects } = useProjectFiltersState( const { activeFilters, toggleFilter, projects, sortProjectBy, sortBy } =
(state) => state useProjectFiltersState((state) => state)
)
const haveActiveFilters = Object.entries(activeFilters).some( const haveActiveFilters = Object.entries(activeFilters).some(
([_key, values]) => values?.length > 0 ([_key, values]) => values?.length > 0
) )
if (!haveActiveFilters) const resultLabel = haveActiveFilters
return ( ? `Showing ${projects?.length} projects with:`
<span className={labelClass}> : `Showing ${projects.length} projects`
{`Showing ${projects.length} projects`}{" "}
</span>
)
return ( return (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span className={labelClass}> <div className="flex items-center justify-between">
{`Showing ${projects?.length} projects with:`}{" "} <span className={labelClass}>{resultLabel}</span>
</span> <Dropdown
<div className="inline-flex flex-wrap gap-1 md:gap-4"> label={`Sort by: ${getSortLabel(sortBy)}`}
{Object.entries(activeFilters).map(([key, filters], index) => { defaultItem="random"
return ( items={projectSortItems}
<> onChange={(sortBy) => sortProjectBy(sortBy as ProjectSortBy)}
{filters?.map((filter) => { disabled={!projects?.length}
if (filter?.length === 0) return null />
return (
<CategoryTag
closable
variant="gray"
onClose={() =>
toggleFilter({
tag: key as ProjectFilter,
value: filter,
})
}
key={`${index}-${filter}`}
>
{filter}
</CategoryTag>
)
})}
</>
)
})}
</div> </div>
{haveActiveFilters && (
<div className="inline-flex flex-wrap gap-1 md:gap-4">
{Object.entries(activeFilters).map(([key, filters], index) => {
return (
<>
{filters?.map((filter) => {
if (filter?.length === 0) return null
return (
<CategoryTag
closable
variant="gray"
onClose={() =>
toggleFilter({
tag: key as ProjectFilter,
value: filter,
})
}
key={`${index}-${filter}`}
>
{filter}
</CategoryTag>
)
})}
</>
)
})}
</div>
)}
</div> </div>
) )
} }

View File

@@ -6,7 +6,7 @@ import { LucideIcon } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center duration-100 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background", "font-sans inline-flex items-center justify-center duration-100 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
{ {
variants: { variants: {
variant: { variant: {

View File

@@ -0,0 +1,87 @@
import React, { useState } from "react"
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
import { cn } from "@/lib/utils"
import { Icons } from "../icons"
interface DropdownItemProps {
label: string
value?: string | number
}
interface DropdownProps {
label: string
items?: DropdownItemProps[]
defaultItem?: string | number
onChange?: (value: DropdownItemProps["value"]) => void
disabled?: boolean
}
const Dropdown = ({
label,
onChange,
defaultItem,
disabled,
items,
}: DropdownProps) => {
const [selected, setSelected] =
useState<DropdownItemProps["value"]>(defaultItem)
const onSelectCallback = ({ value }: DropdownItemProps) => {
setSelected(value)
if (typeof onChange === "function") onChange(value)
}
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild disabled={disabled}>
<button
className={cn("focus:outline-none ring-0", {
"opacity-70 cursor-not-allowed": disabled,
})}
aria-label="dropdown menu"
>
<div className="flex items-center gap-1">
<span className="text-sm font-medium break-words text-tuatara-950">
{label}
</span>
<Icons.arrowDown />
</div>
</button>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="min-w-[136px] max-h-[250px] overflow-scroll border border-tuatara-200 rounded-md bg-white py-2"
sideOffset={5}
>
{items?.map((item, index) => {
const active = selected === item.value
return (
<DropdownMenu.Item
key={index}
className={cn(
"relative py-3 px-5 w-full font-sans text-sm cursor-pointer hover:font-medium focus:outline-none ring-0 hover:text-anakiwa-500 text-duration-200",
{
"text-tuatara-950 font-normal": !active,
"text-anakiwa-500 font-medium": active,
}
)}
onSelect={() => onSelectCallback(item)}
>
{active && (
<div className="bg-anakiwa-500 w-[3px] absolute left-0 top-0 bottom-0"></div>
)}
{item.label}
</DropdownMenu.Item>
)
})}
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
)
}
Dropdown.displayName = "Dropdown"
export { Dropdown }

View File

@@ -7,7 +7,7 @@ Anon Aadhaar is a project that allows individuals to prove their citizenship ano
export const anonAadhaar: ProjectInterface = { export const anonAadhaar: ProjectInterface = {
id: "anon-aadhaar", id: "anon-aadhaar",
projectStatus: "active", projectStatus: "active",
image: "", image: "anon-aadhaar.svg",
name: "Anon Aadhaar", name: "Anon Aadhaar",
tldr: "Tools for building build privacy-preserving applications using government ID cards, specifically Aadhaar cards in India.", tldr: "Tools for building build privacy-preserving applications using government ID cards, specifically Aadhaar cards in India.",
description, description,
@@ -17,7 +17,7 @@ export const anonAadhaar: ProjectInterface = {
tags: { tags: {
keywords: ["Anonymity/privacy", "Social", "Identity", "Voting/governance"], keywords: ["Anonymity/privacy", "Social", "Identity", "Voting/governance"],
themes: ["build", "play"], themes: ["build", "play"],
type: ["Legos/dev tools", "Lego sets/toolkits", "Proof of concept"], types: ["Legos/dev tools", "Lego sets/toolkits", "Proof of concept"],
builtWith: ["circom", "rsa"], builtWith: ["circom", "rsa"],
}, },
} }

View File

@@ -24,7 +24,7 @@ export const anonKlub: ProjectInterface = {
"Voting/governance", "Voting/governance",
], ],
themes: ["build", "play"], themes: ["build", "play"],
type: ["Infrastructure/protocol", "Prototype", "Proof of concept"], types: ["Infrastructure/protocol", "Prototype", "Proof of concept"],
builtWith: ["circom", "snarkjs", "halo2"], builtWith: ["circom", "snarkjs", "halo2"],
}, },
} }

View File

@@ -27,7 +27,7 @@ export const bandada: ProjectInterface = {
"Scaling", "Scaling",
"Key management", "Key management",
], ],
type: [ types: [
"Legos/dev tools", "Legos/dev tools",
"Lego sets/toolkits", "Lego sets/toolkits",
"Prototype", "Prototype",

View File

@@ -19,7 +19,7 @@ export const channel4: ProjectInterface = {
tags: { tags: {
keywords: ["Scaling"], keywords: ["Scaling"],
themes: ["play"], themes: ["play"],
type: ["Application"], types: ["Application"],
builtWith: ["state channel", "smart contract"], builtWith: ["state channel", "smart contract"],
}, },
} }

View File

@@ -15,7 +15,7 @@ export const dslWorkingGroup: ProjectInterface = {
github: "https://github.com/privacy-scaling-explorations/chiquito/", github: "https://github.com/privacy-scaling-explorations/chiquito/",
}, },
tags: { tags: {
type: ["Legos/dev tools", "Proof of concept", "Developer tooling"], types: ["Legos/dev tools", "Proof of concept", "Developer tooling"],
keywords: [], keywords: [],
themes: ["research"], themes: ["research"],
builtWith: [], builtWith: [],

View File

@@ -17,7 +17,7 @@ export const eigenTrust: ProjectInterface = {
tags: { tags: {
keywords: ["Reputation", "Identity"], keywords: ["Reputation", "Identity"],
themes: ["build"], themes: ["build"],
type: ["Infrastructure/protocol"], types: ["Infrastructure/protocol"],
builtWith: ["ethereum attestation service", "halo2", "ethers.rs"], builtWith: ["ethereum attestation service", "halo2", "ethers.rs"],
}, },
} }

View File

@@ -18,7 +18,7 @@ export const pollenLabs: ProjectInterface = {
tags: { tags: {
keywords: ["Anonymity/privacy", "Scaling"], keywords: ["Anonymity/privacy", "Scaling"],
themes: ["play"], themes: ["play"],
type: ["Application"], types: ["Application"],
builtWith: [], builtWith: [],
}, },
} }

View File

@@ -7,7 +7,7 @@ Rate-Limiting Nullifier (RLN) is a protocol designed to combat spam and denial o
export const rln: ProjectInterface = { export const rln: ProjectInterface = {
id: "rln", id: "rln",
projectStatus: "active", projectStatus: "active",
image: "rln.webp", image: "rln.svg",
name: "Rate-Limiting Nullifier", name: "Rate-Limiting Nullifier",
tldr: "A protocol for deterring spam and maintaining anonymity in communication systems.", tldr: "A protocol for deterring spam and maintaining anonymity in communication systems.",
description, description,

View File

@@ -17,7 +17,7 @@ export const summa: ProjectInterface = {
tags: { tags: {
keywords: ["Anonymity/privacy", "Computational Integrity"], keywords: ["Anonymity/privacy", "Computational Integrity"],
themes: ["build", "play"], themes: ["build", "play"],
type: ["Infrastructure/protocol", "Application"], types: ["Infrastructure/protocol", "Application"],
builtWith: ["halo2"], builtWith: ["halo2"],
}, },
} }

View File

@@ -18,7 +18,7 @@ export const tlsn: ProjectInterface = {
}, },
tags: { tags: {
themes: ["build"], themes: ["build"],
type: [], types: [],
builtWith: [], builtWith: [],
keywords: [], keywords: [],
}, },

View File

@@ -15,7 +15,7 @@ export const trustedSetups: ProjectInterface = {
}, },
tags: { tags: {
themes: ["play"], themes: ["play"],
type: ["Legos/dev tools", "Lego sets/toolkits"], types: ["Legos/dev tools", "Lego sets/toolkits"],
builtWith: [], builtWith: [],
keywords: ["Scaling", "Education"], keywords: ["Scaling", "Education"],
}, },

View File

@@ -20,7 +20,7 @@ export const unirepProtocol: ProjectInterface = {
tags: { tags: {
keywords: ["Anonymity/privacy", "Social", "Identity", "Reputation"], keywords: ["Anonymity/privacy", "Social", "Identity", "Reputation"],
themes: ["build"], themes: ["build"],
type: ["Legos/dev tools, Protocol"], types: ["Legos/dev tools, Protocol"],
builtWith: ["semaphore", "circom"], builtWith: ["semaphore", "circom"],
}, },
} }

View File

@@ -18,7 +18,7 @@ export const zk3: ProjectInterface = {
}, },
tags: { tags: {
themes: ["play"], themes: ["play"],
type: [ types: [
"Legos/dev tools", "Legos/dev tools",
"Lego sets/toolkits", "Lego sets/toolkits",
"Infrastructure/protocol", "Infrastructure/protocol",

View File

@@ -17,7 +17,7 @@ export const zkml: ProjectInterface = {
tags: { tags: {
keywords: ["Anonymity/privacy", "Scaling"], keywords: ["Anonymity/privacy", "Scaling"],
themes: ["research"], themes: ["research"],
type: ["Proof of concept", "Infrastructure/protocol"], types: ["Proof of concept", "Infrastructure/protocol"],
builtWith: ["circom", "halo2", "nova"], builtWith: ["circom", "halo2", "nova"],
}, },
} }

View File

@@ -18,8 +18,8 @@ export const zkp2p: ProjectInterface = {
}, },
tags: { tags: {
keywords: ["Private communications"], keywords: ["Private communications"],
theme: ["play"], themes: ["play"],
type: ["Proof of concept", "Application"], types: ["Proof of concept", "Application"],
builtWith: ["circom", "halo2"], builtWith: ["circom", "halo2"],
}, },
} }

View File

@@ -19,6 +19,8 @@ export type ProjectLinkWebsite =
export type ProjectLinkType = Partial<Record<ProjectLinkWebsite, string>> export type ProjectLinkType = Partial<Record<ProjectLinkWebsite, string>>
export type ProjectExtraLinkType = "buildWith" | "play" | "research" | "learn" export type ProjectExtraLinkType = "buildWith" | "play" | "research" | "learn"
export type TagType = "types" | "themes" | "builtWith" | "keywords"
export type ProjectTags = Partial<Record<TagType, string[]>>
export type ActionLinkTypeLink = { export type ActionLinkTypeLink = {
label: string label: string
url: string url: string
@@ -36,6 +38,6 @@ export interface ProjectInterface {
description: string description: string
links?: ProjectLinkType links?: ProjectLinkType
projectStatus: ProjectStatusType projectStatus: ProjectStatusType
tags?: Record<string, string[]> tags?: ProjectTags
extraLinks?: ActionLinkType extraLinks?: ActionLinkType
} }

View File

@@ -23,6 +23,7 @@
"@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
"class-variance-authority": "^0.4.0", "class-variance-authority": "^0.4.0",
"clsx": "^1.2.1", "clsx": "^1.2.1",

205
pnpm-lock.yaml generated
View File

@@ -14,6 +14,9 @@ dependencies:
'@radix-ui/react-dialog': '@radix-ui/react-dialog':
specifier: ^1.0.4 specifier: ^1.0.4
version: 1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0) version: 1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-dropdown-menu':
specifier: ^2.0.5
version: 2.0.5(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': '@radix-ui/react-slot':
specifier: ^1.0.2 specifier: ^1.0.2
version: 1.0.2(@types/react@18.2.7)(react@18.2.0) version: 1.0.2(@types/react@18.2.7)(react@18.2.0)
@@ -360,6 +363,34 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
/@floating-ui/core@1.4.1:
resolution: {integrity: sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==}
dependencies:
'@floating-ui/utils': 0.1.1
dev: false
/@floating-ui/dom@1.5.1:
resolution: {integrity: sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==}
dependencies:
'@floating-ui/core': 1.4.1
'@floating-ui/utils': 0.1.1
dev: false
/@floating-ui/react-dom@2.0.2(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
dependencies:
'@floating-ui/dom': 1.5.1
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@floating-ui/utils@0.1.1:
resolution: {integrity: sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==}
dev: false
/@humanwhocodes/config-array@0.11.8: /@humanwhocodes/config-array@0.11.8:
resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==}
engines: {node: '>=10.10.0'} engines: {node: '>=10.10.0'}
@@ -574,6 +605,35 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false dev: false
/@radix-ui/react-arrow@1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.22.3
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collapsible': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@types/react': 18.2.7
'@types/react-dom': 18.2.4
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-checkbox@1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-checkbox@1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==} resolution: {integrity: sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==}
peerDependencies: peerDependencies:
@@ -755,6 +815,33 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false dev: false
/@radix-ui/react-dropdown-menu@2.0.5(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-xdOrZzOTocqqkCkYo8yRPCib5OkTkqN7lqNCdxwPOdE466DOaNl4N8PkUIlsXthQvW5Wwkd+aEmWpfWlBoDPEw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.22.3
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-menu': 2.0.5(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@types/react': 18.2.7
'@types/react-dom': 18.2.4
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.7)(react@18.2.0): /@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
peerDependencies: peerDependencies:
@@ -807,6 +894,74 @@ packages:
react: 18.2.0 react: 18.2.0
dev: false dev: false
/@radix-ui/react-menu@2.0.5(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Gw4f9pwdH+w5w+49k0gLjN0PfRDHvxmAgG16AbyJZ7zhwZ6PBHKtWohvnSwfusfnK3L68dpBREHpVkj8wEM7ZA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.22.3
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-dismissable-layer': 1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-focus-scope': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-popper': 1.1.2(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-portal': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@types/react': 18.2.7
'@types/react-dom': 18.2.4
aria-hidden: 1.2.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-remove-scroll: 2.5.5(@types/react@18.2.7)(react@18.2.0)
dev: false
/@radix-ui/react-popper@1.1.2(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-1CnGGfFi/bbqtJZZ0P/NQY20xdG3E0LALJaLUEoKwPLwl6PPPfbeiCqMVQnhoFRAxjJj4RpBRJzDmUgsex2tSg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.22.3
'@floating-ui/react-dom': 2.0.2(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-use-rect': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-use-size': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/rect': 1.0.1
'@types/react': 18.2.7
'@types/react-dom': 18.2.4
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-portal@1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-portal@1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA==} resolution: {integrity: sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA==}
peerDependencies: peerDependencies:
@@ -871,6 +1026,35 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false dev: false
/@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.22.3
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.7)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.7)(react@18.2.0)
'@types/react': 18.2.7
'@types/react-dom': 18.2.4
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-slot@1.0.2(@types/react@18.2.7)(react@18.2.0): /@radix-ui/react-slot@1.0.2(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
peerDependencies: peerDependencies:
@@ -958,6 +1142,21 @@ packages:
react: 18.2.0 react: 18.2.0
dev: false dev: false
/@radix-ui/react-use-rect@1.0.1(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@babel/runtime': 7.22.3
'@radix-ui/rect': 1.0.1
'@types/react': 18.2.7
react: 18.2.0
dev: false
/@radix-ui/react-use-size@1.0.1(@types/react@18.2.7)(react@18.2.0): /@radix-ui/react-use-size@1.0.1(@types/react@18.2.7)(react@18.2.0):
resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==} resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==}
peerDependencies: peerDependencies:
@@ -973,6 +1172,12 @@ packages:
react: 18.2.0 react: 18.2.0
dev: false dev: false
/@radix-ui/rect@1.0.1:
resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==}
dependencies:
'@babel/runtime': 7.22.3
dev: false
/@rushstack/eslint-patch@1.3.0: /@rushstack/eslint-patch@1.3.0:
resolution: {integrity: sha512-IthPJsJR85GhOkp3Hvp8zFOPK5ynKn6STyHa/WZpioK7E1aYDiBzpqQPrngc14DszIUkIrdd3k9Iu0XSzlP/1w==} resolution: {integrity: sha512-IthPJsJR85GhOkp3Hvp8zFOPK5ynKn6STyHa/WZpioK7E1aYDiBzpqQPrngc14DszIUkIrdd3k9Iu0XSzlP/1w==}
dev: true dev: true

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 206 KiB

View File

@@ -5,9 +5,24 @@ import { create } from "zustand"
import { ProjectInterface } from "@/lib/types" import { ProjectInterface } from "@/lib/types"
import { uniq } from "@/lib/utils" import { uniq } from "@/lib/utils"
export type ProjectSortBy = "random" | "asc" | "desc" | "relevance"
export type ProjectFilter = "keywords" | "builtWith" | "themes" export type ProjectFilter = "keywords" | "builtWith" | "themes"
export type FiltersProps = Record<ProjectFilter, string[]> export type FiltersProps = Record<ProjectFilter, string[]>
interface ProjectInterfaceScore extends ProjectInterface {
score: number
}
export const SortByFnMapping: Record<
ProjectSortBy,
(a: ProjectInterfaceScore, b: ProjectInterfaceScore) => number
> = {
random: () => Math.random() - 0.5,
asc: (a, b) => a.name.localeCompare(b.name),
desc: (a, b) => b.name.localeCompare(a.name),
relevance: (a, b) => b?.score - a?.score, // sort from most relevant to least relevant
}
export const FilterLabelMapping: Record<ProjectFilter, string> = { export const FilterLabelMapping: Record<ProjectFilter, string> = {
keywords: "Keywords", keywords: "Keywords",
builtWith: "Built with", builtWith: "Built with",
@@ -20,6 +35,7 @@ export const FilterTypeMapping: Record<ProjectFilter, "checkbox" | "button"> = {
themes: "button", themes: "button",
} }
interface ProjectStateProps { interface ProjectStateProps {
sortBy: ProjectSortBy
projects: ProjectInterface[] projects: ProjectInterface[]
filters: FiltersProps filters: FiltersProps
activeFilters: Partial<FiltersProps> activeFilters: Partial<FiltersProps>
@@ -43,6 +59,7 @@ interface ProjectActionsProps {
setFilterFromQueryString: (filters: Partial<FiltersProps>) => void setFilterFromQueryString: (filters: Partial<FiltersProps>) => void
onFilterProject: (searchPattern: string) => void onFilterProject: (searchPattern: string) => void
onSelectTheme: (theme: string, searchPattern?: string) => void onSelectTheme: (theme: string, searchPattern?: string) => void
sortProjectBy: (sortBy: ProjectSortBy) => void
} }
const createURLQueryString = (params: Partial<FiltersProps>): string => { const createURLQueryString = (params: Partial<FiltersProps>): string => {
@@ -141,17 +158,34 @@ export const filterProjects = ({
const fuse = new Fuse(projectList, { const fuse = new Fuse(projectList, {
threshold: 0.2, threshold: 0.2,
useExtendedSearch: true, useExtendedSearch: true,
includeScore: true,
keys, keys,
}) })
const result = fuse.search(query)?.map(({ item }) => item) const result = fuse.search(query)?.map(({ item, score }) => {
return {
...item,
score, // 0 indicates a perfect match, while a score of 1 indicates a complete mismatch.
}
})
return result ?? [] return result ?? []
} }
const sortProjectByFn = (
projects: ProjectInterface[],
sortBy: ProjectSortBy
) => {
const sortedProjectList: ProjectInterface[] = [
...(projects as ProjectInterfaceScore[]),
].sort(SortByFnMapping[sortBy])
return sortedProjectList
}
export const useProjectFiltersState = create< export const useProjectFiltersState = create<
ProjectStateProps & ProjectActionsProps ProjectStateProps & ProjectActionsProps
>()((set) => ({ >()((set) => ({
sortBy: "random",
projects, projects,
queryString: "", queryString: "",
filters: getProjectFilters(), // list of filters with all possible values from projects filters: getProjectFilters(), // list of filters with all possible values from projects
@@ -183,7 +217,7 @@ export const useProjectFiltersState = create<
...state, ...state,
activeFilters, activeFilters,
queryString, queryString,
projects: filteredProjects, projects: sortProjectByFn(filteredProjects, state.sortBy),
} }
}), }),
onSelectTheme: (theme: string, searchQuery = "") => { onSelectTheme: (theme: string, searchQuery = "") => {
@@ -206,7 +240,7 @@ export const useProjectFiltersState = create<
return { return {
...state, ...state,
activeFilters, activeFilters,
projects: filteredProjects, projects: sortProjectByFn(filteredProjects, state.sortBy),
} }
}) })
}, },
@@ -219,7 +253,7 @@ export const useProjectFiltersState = create<
return { return {
...state, ...state,
projects: filteredProjects, projects: sortProjectByFn(filteredProjects, state.sortBy),
} }
}) })
}, },
@@ -232,4 +266,13 @@ export const useProjectFiltersState = create<
} }
}) })
}, },
sortProjectBy(sortBy: ProjectSortBy) {
set((state: any) => {
return {
...state,
sortBy,
projects: sortProjectByFn(state.projects, sortBy),
}
})
},
})) }))

View File

@@ -30,10 +30,12 @@ module.exports = {
input: "hsl(var(--input))", input: "hsl(var(--input))",
ring: "hsl(var(--ring))", ring: "hsl(var(--ring))",
anakiwa: { anakiwa: {
50: "#F2FAFD",
100: "hsl(var(--anakiwa))", 100: "hsl(var(--anakiwa))",
100: "#E4F3FA", 100: "#E4F3FA",
200: "#C2E8F5", 200: "#C2E8F5",
300: "#A3DFF0", 300: "#A3DFF0",
400: "#50C3E0",
500: "#29ACCE", 500: "#29ACCE",
950: "#103241", 950: "#103241",
}, },