blocks/case-studies/filter/use-filtered-cases.tsx (84 lines of code) (raw):

import { CaseItem } from '../case-studies'; import { parseCompose, parsePlatforms, parseType } from '../utils'; import caseStudiesDataRaw from '../../../data/case-studies/case-studies.yml'; import { useRouter } from 'next/router'; import { createContext, ReactNode, useContext, useMemo } from 'react'; const CaseStudiesContext = createContext<CaseItem[]>([]); /** * Returns the list of case studies from a static source filtered according to the URL query parameters. * Supported query parameters: * - type: 'all' | 'multiplatform' | 'server-side' * - platforms: string[] (array of platform ids) * - compose: boolean */ export const useFilteredCases = (): CaseItem[] => { return useContext(CaseStudiesContext); }; /** * Represents the properties for the FilteredCases component. * * @typedef {Object} FilteredCasesProviderProps * * @property {FilterOptions} filter - The filter options used to determine which cases are displayed. * @property {(items: CaseItem[]) => CaseItem[]} [preprocess] - An optional function that can be used to preprocess the case studies before filtering. * @property {ReactNode} children - The child components or elements to be rendered within the FilteredCases component. */ export type FilteredCasesProviderProps = { filter: FilterOptions, preprocess?: (items: CaseItem[]) => CaseItem[] children: ReactNode } /** * FilteredCasesProvider is a React component that provides a filtered list of case study items * through a context. It takes a filter configuration and child components as props. * * @param {FilteredCasesProviderProps} props - The properties passed to the component. */ export const FilteredCasesProvider = (props: FilteredCasesProviderProps) => { const { preprocess, children, filter } = props; const { type, platforms, compose } = filter; const source: CaseItem[] = caseStudiesDataRaw.items; const data = useMemo(() => filterCaseStudies(source, { type, platforms, compose }), [type, platforms, compose, source]); const items = useMemo(() => preprocess ? preprocess(data) : data, [preprocess, data]); return <CaseStudiesContext.Provider value={items}>{children}</CaseStudiesContext.Provider>; }; /** * RouterCasesProvider is a functional component that provides a filter object * derived from the router query parameters to its child components via the * FilteredCasesProvider context. It utilizes the Next.js useRouter hook to * access query parameters and memoizes the filter to optimize re-rendering. * * Props: * - children: React.ReactNode - The child components to render within this provider. */ export const RouterCasesProvider: React.FC = ({ children }) => { const router = useRouter(); const filter = useMemo(() => ({ type: parseType(router.query.type), platforms: parsePlatforms(router.query.platforms), compose: parseCompose(router.query.compose) }), [router.query.type, router.query.platforms, router.query.compose]); return <FilteredCasesProvider filter={filter}>{children}</FilteredCasesProvider>; }; export interface FilterOptions { type?: string | null; platforms?: string[]; compose?: boolean; } export const filterCaseStudies = ( source: CaseItem[], { type, platforms = [], compose = false }: FilterOptions ): CaseItem[] => { /** * Matches if: * - no platform filter is selected * - case has ALL platforms that match the filter * - special case: 'kotlin-multiplatform' case is considered to have all platforms */ const matchesPlatformFilter = (caseItem: CaseItem): boolean => { if (platforms.length === 0) { return true; } const isSpecialKotlinMultiplatformCase = caseItem.id === 'kotlin-multiplatform'; return (platforms).every((platform: string) => (caseItem.platforms || [] as string[]).includes(platform)) || isSpecialKotlinMultiplatformCase; }; /** * Matches if: * - compose filter is not selected * - case has compose-multiplatform platform * - special case: 'kotlin-multiplatform' case is considered to match compose filter */ const matchesComposeFilter = (caseItem: CaseItem): boolean => { if (!compose) { return true; } const isSpecialKotlinMultiplatformWithComposeCase = caseItem.id === 'kotlin-multiplatform'; return (caseItem.platforms || []).includes('compose-multiplatform') || isSpecialKotlinMultiplatformWithComposeCase; }; return source.filter((caseItem) => { const isMultiplatformCase = caseItem.type === 'multiplatform'; const isServerSideCase = caseItem.type === 'server-side'; if (type === 'server-side') { return isServerSideCase; } if (type === 'multiplatform') { return isMultiplatformCase && matchesPlatformFilter(caseItem) && matchesComposeFilter(caseItem); } // for "all" types show all server-side cases if (isServerSideCase) { return true; } // for "all" types show multiplatform cases only if they match the filters return matchesPlatformFilter(caseItem) && matchesComposeFilter(caseItem); }); };