Skip to content

Commit

Permalink
Add search to crag list.
Browse files Browse the repository at this point in the history
  • Loading branch information
salamca committed Feb 25, 2024
1 parent 73f4951 commit 0dac8a9
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import IconStarFull from "../../../../../../components/ui/icons/star-full";
import IconComment from "../../../../../../components/ui/icons/comment";
import IconCheck from "../../../../../../components/ui/icons/check";
import { IconSize } from "@/components/ui/icons/icon-size";
import { filterEntitiesBySearchTerm } from "@/utils/search-helpers";

interface Props {
crag: Crag;
Expand Down Expand Up @@ -71,35 +72,6 @@ function filterRoutesByFilter(
return routes;
}

function escape(searchTerm: string): string {
return searchTerm.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
}

function ignoreAccents(searchTerm: string): string {
return searchTerm
.replace(/[cčć]/gi, "[cčć]")
.replace(/[sš]/gi, "[sš]")
.replace(/[zž]/gi, "[zž]")
.replace(/[aàáâäæãåā]/gi, "[aàáâäæãåā]")
.replace(/[eèéêëēėę]/gi, "[eèéêëēėę]")
.replace(/[iîïíīįì]/gi, "[iîïíīįì]")
.replace(/[oôöòóœøōõ]/gi, "[oôöòóœøōõ]")
.replace(/[uûüùúū]/gi, "[uûüùúū]")
.replace(/[dđ]/gi, "[dđ]");
}

function filterRoutesBySearchTerm(
routes: Route[],
searchTerm: string
): Route[] {
searchTerm = searchTerm.toLowerCase();
searchTerm = escape(searchTerm);
searchTerm = ignoreAccents(searchTerm);
const regExp = new RegExp(searchTerm);

return routes.filter((route) => regExp.test(route.name.toLowerCase()));
}

function sortRoutes(
routes: Route[],
ascents: Map<string, string>,
Expand Down Expand Up @@ -160,7 +132,7 @@ function CragRouteList({ routes, crag, ascents }: Props) {
routes = filterRoutesByFilter(routes, ascents, cragRoutesState.filter);

if (cragRoutesState.search?.query) {
routes = filterRoutesBySearchTerm(routes, cragRoutesState.search.query);
routes = filterEntitiesBySearchTerm(routes, cragRoutesState.search.query);
}

routes = sortRoutes(routes, ascents, cragRoutesState.sort);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function CragRoutesActions() {
: "flex justify-center"
}`}
>
<div className="flex items-center justify-center py-5">
<div className="flex items-center justify-center py-4 xs:py-5">
<div>
<Filter />
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/app/[lang]/crags/components/crag-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function CragCard({ crag, columns }: TCragCardProps) {
crag.rainproof != null;

return (
<div className="border-b border-neutral-200 py-4">
<div className="border-t border-neutral-200 px-4 py-4 xs:px-8 md:px-0">
{/* row 1 */}
<div className="flex items-center justify-between">
<div className="font-medium">
Expand Down
68 changes: 35 additions & 33 deletions src/app/[lang]/crags/components/crag-list-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,42 @@ type TCragListTableProps = {

function CragListTable({ crags, columns }: TCragListTableProps) {
return (
<table className="w-full">
<thead>
<tr className="border-b border-neutral-200 text-left text-neutral-500">
{columns.map((column, index) => {
return (
<th
key={column.name}
className={`p-4 font-normal first:pl-0 last:pr-0 ${
columns.length > 1 ? "last:text-right" : ""
}`}
style={{
minWidth: `${
(column.width -
(index == 0 ? 16 : 0) -
(index == columns.length - 1 ? 16 : 0)) /
16
}rem`,
}}
>
{column.label}
</th>
);
})}
</tr>
</thead>
<div className="px-4 xs:px-8 md:px-0">
<table className="w-full">
<thead>
<tr className="border-b border-neutral-200 text-left text-neutral-500">
{columns.map((column, index) => {
return (
<th
key={column.name}
className={`p-4 font-normal first:pl-0 last:pr-0 ${
columns.length > 1 ? "last:text-right" : ""
}`}
style={{
minWidth: `${
(column.width -
(index == 0 ? 16 : 0) -
(index == columns.length - 1 ? 16 : 0)) /
16
}rem`,
}}
>
{column.label}
</th>
);
})}
</tr>
</thead>

<tbody>
{crags.map((crag: Crag) => (
<Fragment key={crag.id}>
<CragRow crag={crag} columns={columns} />
</Fragment>
))}
</tbody>
</table>
<tbody>
{crags.map((crag: Crag) => (
<Fragment key={crag.id}>
<CragRow crag={crag} columns={columns} />
</Fragment>
))}
</tbody>
</table>
</div>
);
}

Expand Down
54 changes: 44 additions & 10 deletions src/app/[lang]/crags/components/filtered-crags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ import {
parseAsString,
useQueryStates,
} from "next-usequerystate";
import { useCallback, useState } from "react";
import { useCallback, useRef, useState } from "react";
import useResizeObserver from "@/hooks/useResizeObserver";
import CragListCards from "./crag-list-cards";
import CragListTable from "./crag-list-table";
import { filterEntitiesBySearchTerm } from "@/utils/search-helpers";

type TCragListColumn = {
name: string;
Expand Down Expand Up @@ -134,7 +135,7 @@ function FilteredCrags({ crags, countries }: TFilteredCragsProps) {
}

// Filter crags based on filters state
const filteredCrags = crags.filter(
let filteredCrags = crags.filter(
(crag: Crag) =>
//
// country
Expand Down Expand Up @@ -483,6 +484,23 @@ function FilteredCrags({ crags, countries }: TFilteredCragsProps) {
return 0;
});

const [searchQuery, setSearchQuery] = useState("");
const [searchFocus, setSearchFocus] = useState(false);

// Filter crags based on search term
if (searchQuery) {
filteredCrags = filterEntitiesBySearchTerm(filteredCrags, searchQuery);
}

const searchFieldRef = useRef<HTMLInputElement>(null);
const handleSearchIconClick = () => {
setSearchFocus(true);

setTimeout(() => {
searchFieldRef.current && searchFieldRef.current.focus();
}, 0);
};

const [compact, setCompact] = useState<boolean | null>(null);

const neededWidth = selectedColumns
Expand Down Expand Up @@ -525,8 +543,12 @@ function FilteredCrags({ crags, countries }: TFilteredCragsProps) {
for >=md: filter pane is always visible, filter icon dissapears
*/}

<div className="x-auto relative z-10 flex rotate-0 items-center justify-center px-4 py-4 2xl:container xs:px-8 sm:justify-between">
<div className="flex items-center justify-center">
<div
className={`x-auto relative z-10 rotate-0 items-center justify-center px-4 2xl:container xs:px-8 sm:justify-between ${
searchFocus || searchQuery ? "block sm:flex" : "flex justify-center"
}`}
>
<div className="flex items-center justify-center py-4 sm:py-5">
<Button variant="quaternary">
<div className="flex">
<IconMap />
Expand Down Expand Up @@ -603,7 +625,7 @@ function FilteredCrags({ crags, countries }: TFilteredCragsProps) {

<div className="flex items-center sm:hidden">
<div className="ml-3 h-6 border-l border-neutral-300 pr-3"></div>
<Button variant="quaternary">
<Button variant="quaternary" onClick={handleSearchIconClick}>
<IconSearch />
</Button>
</div>
Expand All @@ -614,23 +636,35 @@ function FilteredCrags({ crags, countries }: TFilteredCragsProps) {
</Button>
</div>

<div className="hidden min-w-0 xs:ml-8 xs:w-80 xs:border-none sm:block">
<div
className={`min-w-0 sm:ml-8 sm:w-80 ${
searchFocus || searchQuery
? "mb-6 block sm:mb-0"
: "hidden sm:block"
}`}
>
<TextField
ref={searchFieldRef}
value={searchQuery}
onChange={setSearchQuery}
onBlur={() => setSearchFocus(false)}
prefix={<IconSearch />}
placeholder="Poišči po imenu"
aria-label="Poišči po imenu"
suffix={
<Button variant="quaternary">
<IconClose />
</Button>
searchQuery && (
<Button variant="quaternary" onClick={() => setSearchQuery("")}>
<IconClose />
</Button>
)
}
/>
</div>
</div>

{/* Main content */}

<div className="mx-auto flex items-start px-4 2xl:container xs:px-8">
<div className="xs:pxds-8 pxdsa-4 mx-auto flex items-start 2xl:container md:px-8">
{/* Filters pane */}
{/* on >=md pane is always visible and is displayed as a card
on <md pane slides in from the side when filters are being changed */}
Expand Down
34 changes: 34 additions & 0 deletions src/utils/search-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
function escape(searchTerm: string): string {
return searchTerm.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
}

function ignoreAccents(searchTerm: string): string {
return searchTerm
.replace(/[cčć]/gi, "[cčć]")
.replace(/[sš]/gi, "[sš]")
.replace(/[zž]/gi, "[zž]")
.replace(/[aàáâäæãåā]/gi, "[aàáâäæãåā]")
.replace(/[eèéêëēėę]/gi, "[eèéêëēėę]")
.replace(/[iîïíīįì]/gi, "[iîïíīįì]")
.replace(/[oôöòóœøōõ]/gi, "[oôöòóœøōõ]")
.replace(/[uûüùúū]/gi, "[uûüùúū]")
.replace(/[dđ]/gi, "[dđ]");
}

type THasName = {
name: string;
};

function filterEntitiesBySearchTerm<E extends THasName>(
entities: E[],
searchTerm: string
): E[] {
searchTerm = searchTerm.toLowerCase();
searchTerm = escape(searchTerm);
searchTerm = ignoreAccents(searchTerm);
const regExp = new RegExp(searchTerm);

return entities.filter((entity) => regExp.test(entity.name.toLowerCase()));
}

export { filterEntitiesBySearchTerm };

0 comments on commit 0dac8a9

Please sign in to comment.