From e3474ff6f8aaf6d2cca2e2a4f9b28a39ef681e1c Mon Sep 17 00:00:00 2001 From: Kalidou Diagne Date: Wed, 6 Sep 2023 17:17:54 +0100 Subject: [PATCH 01/17] add filter functionality and project sorting --- components/icons.tsx | 15 ++ components/project/project-result-bar.tsx | 88 ++++---- components/ui/dropdown.tsx | 78 +++++++ package.json | 1 + pnpm-lock.yaml | 235 ++++++++++++++++++++++ state/useProjectFiltersState.ts | 27 ++- 6 files changed, 405 insertions(+), 39 deletions(-) create mode 100644 components/ui/dropdown.tsx diff --git a/components/icons.tsx b/components/icons.tsx index 4717940..b68c0e7 100644 --- a/components/icons.tsx +++ b/components/icons.tsx @@ -127,4 +127,19 @@ export const Icons = { /> ), + arrowDown: (props: LucideProps) => ( + + + + ), } diff --git a/components/project/project-result-bar.tsx b/components/project/project-result-bar.tsx index aed40ba..027c5d0 100644 --- a/components/project/project-result-bar.tsx +++ b/components/project/project-result-bar.tsx @@ -2,61 +2,73 @@ import { ProjectFilter, + ProjectSortBy, useProjectFiltersState, } from "@/state/useProjectFiltersState" 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 projectSortItems: { label: string; value: ProjectSortBy }[] = [ + { label: "Random", value: "random" }, + { label: "Title: A-Z", value: "asc" }, + { label: "Title: Z-A", value: "desc" }, +] + export const ProjectResultBar = () => { - const { activeFilters, toggleFilter, projects } = useProjectFiltersState( - (state) => state - ) + const { activeFilters, toggleFilter, projects, sortProjectBy } = + useProjectFiltersState((state) => state) const haveActiveFilters = Object.entries(activeFilters).some( ([_key, values]) => values?.length > 0 ) - if (!haveActiveFilters) - return ( - - {`Showing ${projects.length} projects`}{" "} - - ) + const resultLabel = haveActiveFilters + ? `Showing ${projects?.length} projects with:` + : `Showing ${projects.length} projects` return (
- - {`Showing ${projects?.length} projects with:`}{" "} - -
- {Object.entries(activeFilters).map(([key, filters], index) => { - return ( - <> - {filters?.map((filter) => { - if (filter?.length === 0) return null - - return ( - - toggleFilter({ - tag: key as ProjectFilter, - value: filter, - }) - } - key={`${index}-${filter}`} - > - {filter} - - ) - })} - - ) - })} +
+ {resultLabel} + sortProjectBy(sortBy as ProjectSortBy)} + />
+ {haveActiveFilters && ( +
+ {Object.entries(activeFilters).map(([key, filters], index) => { + return ( + <> + {filters?.map((filter) => { + if (filter?.length === 0) return null + + return ( + + toggleFilter({ + tag: key as ProjectFilter, + value: filter, + }) + } + key={`${index}-${filter}`} + > + {filter} + + ) + })} + + ) + })} +
+ )}
) } diff --git a/components/ui/dropdown.tsx b/components/ui/dropdown.tsx new file mode 100644 index 0000000..3253d81 --- /dev/null +++ b/components/ui/dropdown.tsx @@ -0,0 +1,78 @@ +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 +} + +const Dropdown = ({ label, onChange, defaultItem, items }: DropdownProps) => { + const [selected, setSelected] = + useState(defaultItem) + + const onSelectCallback = ({ value }: DropdownItemProps) => { + setSelected(value) + if (typeof onChange === "function") onChange(value) + } + + return ( + + + + + + + + {items?.map((item, index) => { + const active = selected === item.value + return ( + onSelectCallback(item)} + > + {active && ( +
+ )} + {item.label} +
+ ) + })} +
+
+
+ ) +} + +Dropdown.displayName = "Dropdown" +export { Dropdown } diff --git a/package.json b/package.json index 1f1e0fa..6355a62 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "dependencies": { "@radix-ui/react-checkbox": "^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", "class-variance-authority": "^0.4.0", "clsx": "^1.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22d8ba9..6327498 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: '@radix-ui/react-dialog': 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) + '@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': specifier: ^1.0.2 version: 1.0.2(@types/react@18.2.7)(react@18.2.0) @@ -357,6 +360,34 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 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: resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} engines: {node: '>=10.10.0'} @@ -542,6 +573,27 @@ packages: '@babel/runtime': 7.22.3 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/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) + '@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): resolution: {integrity: sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==} peerDependencies: @@ -570,6 +622,30 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@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): + resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} + 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/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-slot': 1.0.2(@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-compose-refs@1.0.1(@types/react@18.2.7)(react@18.2.0): resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: @@ -632,6 +708,20 @@ packages: react-remove-scroll: 2.5.5(@types/react@18.2.7)(react@18.2.0) dev: false + /@radix-ui/react-direction@1.0.1(@types/react@18.2.7)(react@18.2.0): + resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.22.3 + '@types/react': 18.2.7 + react: 18.2.0 + dev: false + /@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): resolution: {integrity: sha512-7UpBa/RKMoHJYjie1gkF1DlK8l1fdU/VKDpoS3rCCo8YBJR294GwcEHyxHw72yvphJ7ld0AXEcSLAzY2F/WyCg==} peerDependencies: @@ -657,6 +747,33 @@ packages: react-dom: 18.2.0(react@18.2.0) 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): resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} peerDependencies: @@ -709,6 +826,74 @@ packages: react: 18.2.0 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): resolution: {integrity: sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA==} peerDependencies: @@ -773,6 +958,35 @@ packages: react-dom: 18.2.0(react@18.2.0) 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): resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} peerDependencies: @@ -860,6 +1074,21 @@ packages: react: 18.2.0 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): resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==} peerDependencies: @@ -875,6 +1104,12 @@ packages: react: 18.2.0 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: resolution: {integrity: sha512-IthPJsJR85GhOkp3Hvp8zFOPK5ynKn6STyHa/WZpioK7E1aYDiBzpqQPrngc14DszIUkIrdd3k9Iu0XSzlP/1w==} dev: true diff --git a/state/useProjectFiltersState.ts b/state/useProjectFiltersState.ts index 08b2dae..2372def 100644 --- a/state/useProjectFiltersState.ts +++ b/state/useProjectFiltersState.ts @@ -3,11 +3,21 @@ import Fuse from "fuse.js" import { create } from "zustand" import { ProjectInterface } from "@/lib/types" -import { uniq } from "@/lib/utils" +import { shuffleArray, uniq } from "@/lib/utils" +export type ProjectSortBy = "random" | "asc" | "desc" export type ProjectFilter = "keywords" | "builtWith" | "themes" export type FiltersProps = Record +export const SortByFnMapping: Record< + ProjectSortBy, + (a: ProjectInterface, b: ProjectInterface) => number +> = { + random: () => Math.random() - 0.5, + asc: (a, b) => a.name.localeCompare(b.name), + desc: (a, b) => b.name.localeCompare(a.name), +} + export const FilterLabelMapping: Record = { keywords: "Keywords", builtWith: "Built with", @@ -43,6 +53,7 @@ interface ProjectActionsProps { setFilterFromQueryString: (filters: Partial) => void onFilterProject: (searchPattern: string) => void onSelectTheme: (theme: string, searchPattern?: string) => void + sortProjectBy: (sortBy: ProjectSortBy) => void } const createURLQueryString = (params: Partial): string => { @@ -232,4 +243,18 @@ export const useProjectFiltersState = create< } }) }, + sortProjectBy(sortBy: ProjectSortBy) { + set((state: any) => { + const currentList = state.projects + + const sortedProjectList: ProjectInterface[] = currentList.sort( + SortByFnMapping[sortBy] + ) + + return { + ...state, + projects: sortedProjectList, + } + }) + }, })) From 0943465e3dac640321a9c78b6b89c19a518de84a Mon Sep 17 00:00:00 2001 From: Kalidou Diagne Date: Wed, 6 Sep 2023 17:36:51 +0100 Subject: [PATCH 02/17] add filter sorting --- components/project/project-result-bar.tsx | 1 + components/ui/dropdown.tsx | 15 +++++++++--- state/useProjectFiltersState.ts | 28 +++++++++++++++-------- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/components/project/project-result-bar.tsx b/components/project/project-result-bar.tsx index 027c5d0..c01480d 100644 --- a/components/project/project-result-bar.tsx +++ b/components/project/project-result-bar.tsx @@ -38,6 +38,7 @@ export const ProjectResultBar = () => { defaultItem="random" items={projectSortItems} onChange={(sortBy) => sortProjectBy(sortBy as ProjectSortBy)} + disabled={!projects?.length} />
{haveActiveFilters && ( diff --git a/components/ui/dropdown.tsx b/components/ui/dropdown.tsx index 3253d81..d21f332 100644 --- a/components/ui/dropdown.tsx +++ b/components/ui/dropdown.tsx @@ -15,9 +15,16 @@ interface DropdownProps { items?: DropdownItemProps[] defaultItem?: string | number onChange?: (value: DropdownItemProps["value"]) => void + disabled?: boolean } -const Dropdown = ({ label, onChange, defaultItem, items }: DropdownProps) => { +const Dropdown = ({ + label, + onChange, + defaultItem, + disabled, + items, +}: DropdownProps) => { const [selected, setSelected] = useState(defaultItem) @@ -28,9 +35,11 @@ const Dropdown = ({ label, onChange, defaultItem, items }: DropdownProps) => { return ( - + + + + + + + + + ) +} diff --git a/components/ui/button.tsx b/components/ui/button.tsx index 57ecf84..1ee0b5f 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -6,7 +6,7 @@ import { LucideIcon } from "lucide-react" import { cn } from "@/lib/utils" 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: { variant: { diff --git a/public/icons/emotion-sad-line.svg b/public/icons/emotion-sad-line.svg new file mode 100644 index 0000000..9e2cf99 --- /dev/null +++ b/public/icons/emotion-sad-line.svg @@ -0,0 +1,3 @@ + + + diff --git a/tailwind.config.js b/tailwind.config.js index e81e7d5..c5bea3d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -30,10 +30,12 @@ module.exports = { input: "hsl(var(--input))", ring: "hsl(var(--ring))", anakiwa: { + 50: "#F2FAFD", 100: "hsl(var(--anakiwa))", 100: "#E4F3FA", 200: "#C2E8F5", 300: "#A3DFF0", + 400: "#50C3E0", 500: "#29ACCE", 950: "#103241", }, From 81df18aef45a19b914ac897ad34f743c3f84df5f Mon Sep 17 00:00:00 2001 From: Kalidou Diagne Date: Fri, 8 Sep 2023 11:38:01 +0100 Subject: [PATCH 09/17] remove unused --- app/not-found.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/not-found.tsx b/app/not-found.tsx index a545ccf..95053c7 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -21,7 +21,6 @@ export default function NotFound() {
- {" "}
Date: Fri, 8 Sep 2023 19:34:02 +0100 Subject: [PATCH 10/17] better typing for projects --- data/projects/anon-aadhaar.ts | 2 +- data/projects/anon-klub.ts | 2 +- data/projects/bandada.ts | 2 +- data/projects/channel-4.ts | 2 +- data/projects/dsl-working-group.ts | 2 +- data/projects/eigen-trust.ts | 2 +- data/projects/pollen-labs.ts | 2 +- data/projects/summa.ts | 2 +- data/projects/tlsn.ts | 2 +- data/projects/trusted-setups.ts | 2 +- data/projects/unirep-protocol.ts | 2 +- data/projects/zk3.ts | 2 +- data/projects/zkml.ts | 2 +- data/projects/zkp2p.ts | 4 ++-- lib/types.ts | 4 +++- 15 files changed, 18 insertions(+), 16 deletions(-) diff --git a/data/projects/anon-aadhaar.ts b/data/projects/anon-aadhaar.ts index f39d558..5b8d3df 100644 --- a/data/projects/anon-aadhaar.ts +++ b/data/projects/anon-aadhaar.ts @@ -17,7 +17,7 @@ export const anonAadhaar: ProjectInterface = { tags: { keywords: ["Anonymity/privacy", "Social", "Identity", "Voting/governance"], 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"], }, } diff --git a/data/projects/anon-klub.ts b/data/projects/anon-klub.ts index 2d98868..0e192c5 100644 --- a/data/projects/anon-klub.ts +++ b/data/projects/anon-klub.ts @@ -24,7 +24,7 @@ export const anonKlub: ProjectInterface = { "Voting/governance", ], themes: ["build", "play"], - type: ["Infrastructure/protocol", "Prototype", "Proof of concept"], + types: ["Infrastructure/protocol", "Prototype", "Proof of concept"], builtWith: ["circom", "snarkjs", "halo2"], }, } diff --git a/data/projects/bandada.ts b/data/projects/bandada.ts index 7452f79..c5f4122 100644 --- a/data/projects/bandada.ts +++ b/data/projects/bandada.ts @@ -27,7 +27,7 @@ export const bandada: ProjectInterface = { "Scaling", "Key management", ], - type: [ + types: [ "Legos/dev tools", "Lego sets/toolkits", "Prototype", diff --git a/data/projects/channel-4.ts b/data/projects/channel-4.ts index 20fce33..121f9de 100644 --- a/data/projects/channel-4.ts +++ b/data/projects/channel-4.ts @@ -19,7 +19,7 @@ export const channel4: ProjectInterface = { tags: { keywords: ["Scaling"], themes: ["play"], - type: ["Application"], + types: ["Application"], builtWith: ["state channel", "smart contract"], }, } diff --git a/data/projects/dsl-working-group.ts b/data/projects/dsl-working-group.ts index cc28ba2..1728b3e 100644 --- a/data/projects/dsl-working-group.ts +++ b/data/projects/dsl-working-group.ts @@ -15,7 +15,7 @@ export const dslWorkingGroup: ProjectInterface = { github: "https://github.com/privacy-scaling-explorations/chiquito/", }, tags: { - type: ["Legos/dev tools", "Proof of concept", "Developer tooling"], + types: ["Legos/dev tools", "Proof of concept", "Developer tooling"], keywords: [], themes: ["research"], builtWith: [], diff --git a/data/projects/eigen-trust.ts b/data/projects/eigen-trust.ts index edbe216..d39187e 100644 --- a/data/projects/eigen-trust.ts +++ b/data/projects/eigen-trust.ts @@ -17,7 +17,7 @@ export const eigenTrust: ProjectInterface = { tags: { keywords: ["Reputation", "Identity"], themes: ["build"], - type: ["Infrastructure/protocol"], + types: ["Infrastructure/protocol"], builtWith: ["ethereum attestation service", "halo2", "ethers.rs"], }, } diff --git a/data/projects/pollen-labs.ts b/data/projects/pollen-labs.ts index a2ffbea..60816ae 100644 --- a/data/projects/pollen-labs.ts +++ b/data/projects/pollen-labs.ts @@ -18,7 +18,7 @@ export const pollenLabs: ProjectInterface = { tags: { keywords: ["Anonymity/privacy", "Scaling"], themes: ["play"], - type: ["Application"], + types: ["Application"], builtWith: [], }, } diff --git a/data/projects/summa.ts b/data/projects/summa.ts index 6071b03..a1dbdea 100644 --- a/data/projects/summa.ts +++ b/data/projects/summa.ts @@ -17,7 +17,7 @@ export const summa: ProjectInterface = { tags: { keywords: ["Anonymity/privacy", "Computational Integrity"], themes: ["build", "play"], - type: ["Infrastructure/protocol", "Application"], + types: ["Infrastructure/protocol", "Application"], builtWith: ["halo2"], }, } diff --git a/data/projects/tlsn.ts b/data/projects/tlsn.ts index e7dc2b8..fbe14b2 100644 --- a/data/projects/tlsn.ts +++ b/data/projects/tlsn.ts @@ -18,7 +18,7 @@ export const tlsn: ProjectInterface = { }, tags: { themes: ["build"], - type: [], + types: [], builtWith: [], keywords: [], }, diff --git a/data/projects/trusted-setups.ts b/data/projects/trusted-setups.ts index 9560cf3..e08b792 100644 --- a/data/projects/trusted-setups.ts +++ b/data/projects/trusted-setups.ts @@ -15,7 +15,7 @@ export const trustedSetups: ProjectInterface = { }, tags: { themes: ["play"], - type: ["Legos/dev tools", "Lego sets/toolkits"], + types: ["Legos/dev tools", "Lego sets/toolkits"], builtWith: [], keywords: ["Scaling", "Education"], }, diff --git a/data/projects/unirep-protocol.ts b/data/projects/unirep-protocol.ts index 1496ced..12299dd 100644 --- a/data/projects/unirep-protocol.ts +++ b/data/projects/unirep-protocol.ts @@ -20,7 +20,7 @@ export const unirepProtocol: ProjectInterface = { tags: { keywords: ["Anonymity/privacy", "Social", "Identity", "Reputation"], themes: ["build"], - type: ["Legos/dev tools, Protocol"], + types: ["Legos/dev tools, Protocol"], builtWith: ["semaphore", "circom"], }, } diff --git a/data/projects/zk3.ts b/data/projects/zk3.ts index 7950a27..1b4feb5 100644 --- a/data/projects/zk3.ts +++ b/data/projects/zk3.ts @@ -18,7 +18,7 @@ export const zk3: ProjectInterface = { }, tags: { themes: ["play"], - type: [ + types: [ "Legos/dev tools", "Lego sets/toolkits", "Infrastructure/protocol", diff --git a/data/projects/zkml.ts b/data/projects/zkml.ts index a40396d..6c7e43b 100644 --- a/data/projects/zkml.ts +++ b/data/projects/zkml.ts @@ -17,7 +17,7 @@ export const zkml: ProjectInterface = { tags: { keywords: ["Anonymity/privacy", "Scaling"], themes: ["research"], - type: ["Proof of concept", "Infrastructure/protocol"], + types: ["Proof of concept", "Infrastructure/protocol"], builtWith: ["circom", "halo2", "nova"], }, } diff --git a/data/projects/zkp2p.ts b/data/projects/zkp2p.ts index 2deffc1..12d5a56 100644 --- a/data/projects/zkp2p.ts +++ b/data/projects/zkp2p.ts @@ -18,8 +18,8 @@ export const zkp2p: ProjectInterface = { }, tags: { keywords: ["Private communications"], - theme: ["play"], - type: ["Proof of concept", "Application"], + themes: ["play"], + types: ["Proof of concept", "Application"], builtWith: ["circom", "halo2"], }, } diff --git a/lib/types.ts b/lib/types.ts index 4763ac6..9069ab5 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -19,6 +19,8 @@ export type ProjectLinkWebsite = export type ProjectLinkType = Partial> export type ProjectExtraLinkType = "buildWith" | "play" | "research" | "learn" +export type TagType = "types" | "themes" | "builtWith" | "keywords" +export type ProjectTags = Partial> export type ActionLinkTypeLink = { label: string url: string @@ -36,6 +38,6 @@ export interface ProjectInterface { description: string links?: ProjectLinkType projectStatus: ProjectStatusType - tags?: Record + tags?: ProjectTags extraLinks?: ActionLinkType } From d36e936c86bf7acdaab0fdbcadbcb496430ca67d Mon Sep 17 00:00:00 2001 From: Kalidou Diagne Date: Fri, 8 Sep 2023 21:01:02 +0200 Subject: [PATCH 11/17] Update components/project/project-result-bar.tsx Co-authored-by: Y6NDR <19380973+thebeyondr@users.noreply.github.com> --- components/project/project-result-bar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/project/project-result-bar.tsx b/components/project/project-result-bar.tsx index 3e9ff0c..7bc837e 100644 --- a/components/project/project-result-bar.tsx +++ b/components/project/project-result-bar.tsx @@ -15,7 +15,7 @@ const projectSortItems: { label: string; value: ProjectSortBy }[] = [ { label: "Random", value: "random" }, { label: "Title: A-Z", value: "asc" }, { label: "Title: Z-A", value: "desc" }, - { label: "Relevancy", value: "relevancy" }, + { label: "Relevance", value: "relevance" }, ] const getSortLabel = (sortBy: ProjectSortBy) => { From bcd400c749d792535c4da5ef849b991d3c28c7ac Mon Sep 17 00:00:00 2001 From: Kalidou Diagne Date: Fri, 8 Sep 2023 21:01:11 +0200 Subject: [PATCH 12/17] Update state/useProjectFiltersState.ts Co-authored-by: Y6NDR <19380973+thebeyondr@users.noreply.github.com> --- state/useProjectFiltersState.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state/useProjectFiltersState.ts b/state/useProjectFiltersState.ts index 6e2e981..790f728 100644 --- a/state/useProjectFiltersState.ts +++ b/state/useProjectFiltersState.ts @@ -20,7 +20,7 @@ export const SortByFnMapping: Record< random: () => Math.random() - 0.5, asc: (a, b) => a.name.localeCompare(b.name), desc: (a, b) => b.name.localeCompare(a.name), - relevancy: (a, b) => b?.score - a?.score, // sort from most relevant to least relevant + relevance: (a, b) => b?.score - a?.score, // sort from most relevant to least relevant } export const FilterLabelMapping: Record = { From 46d3a05fcde437af1962769e68bd64e06abe63cb Mon Sep 17 00:00:00 2001 From: Kalidou Diagne Date: Fri, 8 Sep 2023 20:07:19 +0100 Subject: [PATCH 13/17] fix types --- state/useProjectFiltersState.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state/useProjectFiltersState.ts b/state/useProjectFiltersState.ts index 790f728..399eb28 100644 --- a/state/useProjectFiltersState.ts +++ b/state/useProjectFiltersState.ts @@ -5,7 +5,7 @@ import { create } from "zustand" import { ProjectInterface } from "@/lib/types" import { uniq } from "@/lib/utils" -export type ProjectSortBy = "random" | "asc" | "desc" | "relevancy" +export type ProjectSortBy = "random" | "asc" | "desc" | "relevance" export type ProjectFilter = "keywords" | "builtWith" | "themes" export type FiltersProps = Record From e022f965c27cb9473c097d932199588b95b1e6cb Mon Sep 17 00:00:00 2001 From: Kalidou Diagne Date: Mon, 11 Sep 2023 11:43:55 +0100 Subject: [PATCH 14/17] replace 404 image --- app/not-found.tsx | 2 +- public/icons/404-search.svg | 9 +++++++++ public/icons/emotion-sad-line.svg | 3 --- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 public/icons/404-search.svg delete mode 100644 public/icons/emotion-sad-line.svg diff --git a/app/not-found.tsx b/app/not-found.tsx index 95053c7..2af8fbf 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -26,7 +26,7 @@ export default function NotFound() { emotion sad diff --git a/public/icons/404-search.svg b/public/icons/404-search.svg new file mode 100644 index 0000000..d87354c --- /dev/null +++ b/public/icons/404-search.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/icons/emotion-sad-line.svg b/public/icons/emotion-sad-line.svg deleted file mode 100644 index 9e2cf99..0000000 --- a/public/icons/emotion-sad-line.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - From 73ff64081ccf3160dcde109809cee1671e32f2c2 Mon Sep 17 00:00:00 2001 From: Kalidou Diagne Date: Tue, 12 Sep 2023 09:09:39 +0100 Subject: [PATCH 15/17] replace svg --- public/icons/404-search.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/icons/404-search.svg b/public/icons/404-search.svg index d87354c..cd5f8fd 100644 --- a/public/icons/404-search.svg +++ b/public/icons/404-search.svg @@ -1,9 +1,9 @@ - - + + - + - + From 123f9767fc37299f8f1f00d7f6a8108d74f154cd Mon Sep 17 00:00:00 2001 From: Kalidou Diagne Date: Tue, 12 Sep 2023 09:11:32 +0100 Subject: [PATCH 16/17] update svg size --- app/not-found.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/not-found.tsx b/app/not-found.tsx index 2af8fbf..c3d7796 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -28,7 +28,7 @@ export default function NotFound() { height={80} src="/icons/404-search.svg" alt="emotion sad" - className="w-12 h-12 mx-auto md:w-20 md:h-20 text-anakiwa-400" + className="w-12 h-12 mx-auto md:w-24 md:h-24 text-anakiwa-400" /> 404 From c857021e98e9ed5cb2a6ba685eb8b36a720b6599 Mon Sep 17 00:00:00 2001 From: Kalidou Diagne Date: Tue, 12 Sep 2023 09:27:42 +0100 Subject: [PATCH 17/17] update project banners --- data/projects/anon-aadhaar.ts | 2 +- data/projects/rln.ts | 2 +- public/project-banners/anon-aadhaar.svg | 24 ++++++++++++++++++++++++ public/project-banners/discreetly.svg | 15 ++++++++++++--- public/project-banners/rln.svg | 17 +++++++++++++++++ 5 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 public/project-banners/anon-aadhaar.svg create mode 100644 public/project-banners/rln.svg diff --git a/data/projects/anon-aadhaar.ts b/data/projects/anon-aadhaar.ts index f39d558..bf352cd 100644 --- a/data/projects/anon-aadhaar.ts +++ b/data/projects/anon-aadhaar.ts @@ -7,7 +7,7 @@ Anon Aadhaar is a project that allows individuals to prove their citizenship ano export const anonAadhaar: ProjectInterface = { id: "anon-aadhaar", projectStatus: "active", - image: "", + image: "anon-aadhaar.svg", name: "Anon Aadhaar", tldr: "Tools for building build privacy-preserving applications using government ID cards, specifically Aadhaar cards in India.", description, diff --git a/data/projects/rln.ts b/data/projects/rln.ts index 80beecf..7fe248b 100644 --- a/data/projects/rln.ts +++ b/data/projects/rln.ts @@ -7,7 +7,7 @@ Rate-Limiting Nullifier (RLN) is a protocol designed to combat spam and denial o export const rln: ProjectInterface = { id: "rln", projectStatus: "active", - image: "rln.webp", + image: "rln.svg", name: "Rate-Limiting Nullifier", tldr: "A protocol for deterring spam and maintaining anonymity in communication systems.", description, diff --git a/public/project-banners/anon-aadhaar.svg b/public/project-banners/anon-aadhaar.svg new file mode 100644 index 0000000..63244af --- /dev/null +++ b/public/project-banners/anon-aadhaar.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/project-banners/discreetly.svg b/public/project-banners/discreetly.svg index ab5727b..449beaf 100644 --- a/public/project-banners/discreetly.svg +++ b/public/project-banners/discreetly.svg @@ -1,4 +1,13 @@ - - - + + + + + + + + + + + + diff --git a/public/project-banners/rln.svg b/public/project-banners/rln.svg new file mode 100644 index 0000000..90f9940 --- /dev/null +++ b/public/project-banners/rln.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + +