From be2a91e9ff1ee4f0f89a381b97d4929ab4ca897a Mon Sep 17 00:00:00 2001 From: Igor Papandinas Date: Thu, 18 Jan 2024 00:22:18 +0100 Subject: [PATCH] feat: Controlled filters from labels picking --- app/page.tsx | 24 +++++++--------- components/contributions-table/row.tsx | 6 +++- components/controlled-table.tsx | 38 ++++++++++++++++++++++++++ components/filters/toolbar.tsx | 6 ++-- contexts/filters.tsx | 13 +++++++++ utils/providers.tsx | 23 ++++++++++++++++ 6 files changed, 91 insertions(+), 19 deletions(-) create mode 100644 components/controlled-table.tsx create mode 100644 contexts/filters.tsx create mode 100644 utils/providers.tsx diff --git a/app/page.tsx b/app/page.tsx index ac3e5af..a65a9fc 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,9 +1,10 @@ -import ContributionsTable from "@/components/contributions-table/table"; -import Toolbar from "@/components/filters/toolbar"; +import ControlledTable from "@/components/controlled-table"; import CtaBanner from "@/components/cta-banner"; import { title } from "@/components/primitives"; import { queryDatabase } from "@/lib/notion"; import { containerStyle } from "@/styles"; +import { PaginatedCustomDataResponse } from "@/types"; +import { Contribution } from "@/types/contribution"; import { SearchParams } from "@/types/filters"; import { processNotionFilters, @@ -21,7 +22,7 @@ export default async function Home({ searchParams }: IHomeProps) { filter, }); const contributions = transformNotionDataToContributions(data); - const items = { + const items: PaginatedCustomDataResponse = { data: contributions, hasMore: data.has_more, nextCursor: data.next_cursor ?? undefined, @@ -38,17 +39,12 @@ export default async function Home({ searchParams }: IHomeProps) {
-
- -
- -
+
+
); diff --git a/components/contributions-table/row.tsx b/components/contributions-table/row.tsx index 59ce7ef..4a5aec7 100644 --- a/components/contributions-table/row.tsx +++ b/components/contributions-table/row.tsx @@ -1,11 +1,12 @@ +import { useState, useRef, useEffect, useMemo } from "react"; import { usePathname, useSearchParams, useRouter } from "next/navigation"; import { Chip } from "@nextui-org/chip"; import { Link } from "@nextui-org/link"; import { Tooltip } from "@nextui-org/tooltip"; import Emoji from "@/components/emoji"; import MyImage from "@/components/ui/image"; +import { useFilters } from "@/contexts/filters"; import { formatDate } from "@/utils/date"; -import { useState, useRef, useEffect, useMemo } from "react"; import { getProjectUrls } from "@/utils/github"; import { findInterestsByProject, @@ -145,6 +146,8 @@ export const Labels = ({ const pathname = usePathname(); const params = useSearchParams(); + const { updateFilter } = useFilters(); + const [visibleLabelCount, setVisibleLabelCount] = useState(1); const containerRef = useRef(null); const indicatorRef = useRef(null); @@ -163,6 +166,7 @@ export const Labels = ({ const handleClick = (label: { type: string; value: string }) => { const paramKey = label.type; + updateFilter(paramKey, label.value); const newUrl = createUrl(paramKey, label.value, pathname, params); router.replace(newUrl, { scroll: false }); }; diff --git a/components/controlled-table.tsx b/components/controlled-table.tsx new file mode 100644 index 0000000..cc5b658 --- /dev/null +++ b/components/controlled-table.tsx @@ -0,0 +1,38 @@ +import React from "react"; +import ContributionsTable from "@/components/contributions-table/table"; +import Toolbar from "@/components/filters/toolbar"; +import { FiltersProvider } from "@/contexts/filters"; +import { containerStyle } from "@/styles"; +import { PaginatedCustomDataResponse } from "@/types"; +import { Contribution } from "@/types/contribution"; +import { SearchParams } from "@/types/filters"; + +interface IControlledTableProps { + filter: any; + items: PaginatedCustomDataResponse; + searchParams: SearchParams; +} +const ControlledTable = ({ + filter, + items, + searchParams, +}: IControlledTableProps) => { + return ( + +
+ +
+ +
+
+
+ ); +}; + +export default ControlledTable; diff --git a/components/filters/toolbar.tsx b/components/filters/toolbar.tsx index bc925c0..81c9619 100644 --- a/components/filters/toolbar.tsx +++ b/components/filters/toolbar.tsx @@ -1,5 +1,6 @@ "use client"; import { useRef } from "react"; +import { useFilters } from "@/contexts/filters"; import { LANGUAGES_OPTIONS, INTERESTS_OPTIONS, @@ -9,7 +10,6 @@ import { LANGUAGES_KEY, PROJECTS_KEY, } from "@/data/filters"; -import { useFilters } from "@/hooks/useFilters"; import useSticky from "@/hooks/useSticky"; import { containerStyle } from "@/styles"; import { SearchParams } from "@/types/filters"; @@ -24,9 +24,7 @@ const Toolbar = ({ searchParams }: IToolbarProps) => { const toolbarRef = useRef(null); const isToolbarSticky = useSticky(toolbarRef); - const { filters, updateFilter, clearFilter, clearAllFilters } = useFilters({ - initialParams: searchParams, - }); + const { filters, updateFilter, clearFilter, clearAllFilters } = useFilters(); const handleSelect = (paramKey: string) => (value: string | null) => { if (value) { diff --git a/contexts/filters.tsx b/contexts/filters.tsx new file mode 100644 index 0000000..26e4111 --- /dev/null +++ b/contexts/filters.tsx @@ -0,0 +1,13 @@ +"use client"; + +import { + IConfigProps, + IFiltersContext, + useFilters as filtersHook, +} from "@/hooks/useFilters"; +import { provideContext } from "@/utils/providers"; + +export const [FiltersProvider, useFilters] = provideContext< + IConfigProps, + IFiltersContext +>(filtersHook); diff --git a/utils/providers.tsx b/utils/providers.tsx new file mode 100644 index 0000000..dc8524d --- /dev/null +++ b/utils/providers.tsx @@ -0,0 +1,23 @@ +import type { FC, ReactNode } from "react"; +import { createContext, useContext } from "react"; + +// This utility generates a context provider from a react hook passed as argument +// Returns an array containing the provider and the consumer hook +export const provideContext = (useProviderContext: (props: P) => T) => { + // automatic typing based on our hook's return type + type ContextType = ReturnType; + type ProviderProps = P & { children?: ReactNode }; + type ProviderType = FC; + + const Context = createContext({} as ContextType); + + const Provider: ProviderType = ({ children, ...props }: ProviderProps) => { + const ctx = useProviderContext(props as P); + + return {children}; + }; + + const useProvidedContext = () => useContext(Context); + + return [Provider, useProvidedContext] as [ProviderType, () => ContextType]; +};