Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consolidate collection jump to functionality in search component. #380

Merged
merged 1 commit into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions components/Header/Header.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ const Primary = styled("div", {
fontFamily: "$northwesternSansRegular",
display: "flex",
height: "$gr5",
marginLeft: "$gr4",

a: {
color: "$purple",
Expand Down Expand Up @@ -152,10 +153,7 @@ const Primary = styled("div", {
[`& ${NavStyled}`]: {
width: "0",
opacity: "0",
},

[`& ${SearchStyled}`]: {
marginRight: "0",
marginLeft: "0",
},
},
},
Expand Down
11 changes: 1 addition & 10 deletions components/Header/Primary.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { render, screen } from "@/test-utils";

import HeaderPrimary from "./Primary";
import React from "react";
import { createDynamicRouteParser } from "next-router-mock/dynamic-routes";
import mockRouter from "next-router-mock";

Expand Down Expand Up @@ -38,16 +39,6 @@ describe("HeaderPrimary", () => {
expect(search).toBeInTheDocument();
});

it("renders the search jump to component", () => {
mockRouter.setCurrentUrl(
"https://devbox.library.northwestern.edu:3000/collections/1c2e2200-c12d-4c7f-8b87-a935c349898a",
);
render(<HeaderPrimary />);

expect(screen.queryByTestId("search-ui-component")).toBeNull();
expect(screen.getByTestId("search-jump-to")).toBeInTheDocument();
});

it("renders browse collections link", () => {
render(<HeaderPrimary />);
const link = screen.getByText("Browse Collections");
Expand Down
13 changes: 1 addition & 12 deletions components/Header/Primary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,10 @@ import Container from "@/components/Shared/Container";
import Link from "next/link";
import Nav from "@/components/Nav/Nav";
import Search from "@/components/Search/Search";
import SearchJumpTo from "@/components/Search/JumpTo";
import useElementPosition from "@/hooks/useElementPosition";
import { useRouter } from "next/router";
import { useSearchState } from "@/context/search-context";

const HeaderPrimary: React.FC = () => {
const router = useRouter();
const isCollectionPage =
router.pathname.includes("collection") && router.query.id;

const [searchActive, setSearchActive] = useState<boolean>(false);

const {
Expand Down Expand Up @@ -48,12 +42,7 @@ const HeaderPrimary: React.FC = () => {
>
<Container>
<PrimaryInner>
{!isCollectionPage && (
<Search isSearchActive={handleIsSearchActive} />
)}
{isCollectionPage && (
<SearchJumpTo isSearchActive={handleIsSearchActive} />
)}
<Search isSearchActive={handleIsSearchActive} />
<Nav>
<Link href="/collections">Browse Collections</Link>
</Nav>
Expand Down
17 changes: 8 additions & 9 deletions components/Search/JumpTo.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,42 +28,41 @@ const HelperStyled = styled("div", {
});

const JumpToListStyled = styled("ul", {
position: "absolute",
left: "0px",
display: "block",
display: "flex",
flexDirection: "column",
alignSelf: "flex-end",
margin: "0",
padding: "0",
background: "$white",
width: "100%",
fontSize: "$gr2",
listStyle: "none",
top: "50px",
border: "1px solid $black10",
boxShadow: "3px 3px 8px #0003",
borderRadius: "3px",
boxShadow: "3px 3px 11px #0001",
});

const JumpItem = styled("li", {
position: "relative",
backgroundColor: "$gray6",
transition: "$dcAll",

"& a": {
display: "block",
padding: "$gr3",
borderTopWidth: "1px",
borderTopColor: "$black20",
borderTopStyle: "solid",
cursor: "pointer",
color: "$purple120",
},

"&[aria-selected='true']": {
background: "$purple10",
fontFamily: "$northwesternSansBold",

[`${HelperStyled}`]: {
color: "$purple10",
background: "$purple",
boxShadow: "2px 2px 5px #0001",
background: "$purple120",
fontFamily: "$northwesternSansRegular",

svg: {
width: "$gr3",
Expand Down
44 changes: 10 additions & 34 deletions components/Search/JumpTo.test.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,19 @@
import { render, screen } from "@/test-utils";
import SearchJumpTo from "@/components/Search/JumpTo";
import userEvent from "@testing-library/user-event";

const mockIsSearchActive = jest.fn();
import SearchJumpTo from "@/components/Search/JumpTo";

describe("SearchJumpTo component", () => {
it("renders the component", () => {
render(<SearchJumpTo isSearchActive={() => ({})} />);
const wrapper = screen.getByTestId("search-jump-to-form");
expect(wrapper).toBeInTheDocument();
});

it("conditionally renders the SearchJumpTo component", async () => {
const user = userEvent.setup();
render(
<div data-testid="page">
<span>Outside search form</span>
<SearchJumpTo isSearchActive={mockIsSearchActive} />
</div>,
);
const form = screen.getByTestId("search-jump-to-form");

await user.type(screen.getByRole("search"), "foo");

// JumpTo should be visible
expect(form).toHaveFormValues({ search: "foo" });
expect(screen.getByRole("listbox"));

// Click outside SearchJumpTo form, it should close JumpTo
await user.click(screen.getByText("Outside search form"));

expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
render(<SearchJumpTo searchFocus={true} searchValue={"foo"} top={45} />);

// Type back in main search input, it should re-open JumpTo
await user.type(screen.getByRole("search"), "baz");
const listbox = screen.getByRole("listbox");
expect(listbox).toBeInTheDocument();
expect(listbox).toHaveStyle({ top: "45px" });

expect(form).toHaveFormValues({
search: "foobaz",
});
expect(screen.getByRole("listbox"));
for (let i = 0; i < listbox.children.length; i++) {
const item = listbox.children[i];
expect(item).toBeInTheDocument();
expect(item).toHaveTextContent("foo");
}
});
});
77 changes: 20 additions & 57 deletions components/Search/JumpTo.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
import React, {
ChangeEvent,
FocusEvent,
useEffect,
useRef,
useState,
} from "react";
import React, { useEffect, useRef, useState } from "react";

import { IconSearch } from "@/components/Shared/SVG/Icons";
import SearchJumpToList from "@/components/Search/JumpToList";
import { SearchStyled } from "./Search.styled";
import Swiper from "swiper";

interface SearchProps {
isSearchActive: (value: boolean) => void;
searchFocus: boolean;
searchValue: string;
top: number;
}

const SearchJumpTo: React.FC<SearchProps> = ({ isSearchActive }) => {
const search = useRef<HTMLTextAreaElement>(null);
const SearchJumpTo: React.FC<SearchProps> = ({
searchFocus,
searchValue,
top,
}) => {
const formRef = useRef<HTMLFormElement>(null);
const [searchValue, setSearchValue] = useState<string>("");
const [searchFocus, setSearchFocus] = useState<boolean>(false);
const [isLoaded, setIsLoaded] = useState<boolean>(false);
const [showJumpTo, setShowJumpTo] = useState<boolean>(false);

React.useEffect(() => {
useEffect(() => {
if (searchFocus) setShowJumpTo(Boolean(searchValue));
}, [searchFocus, searchValue]);

useEffect(() => {
const handleMouseDown = (e: MouseEvent) => {
if (
showJumpTo &&
Expand Down Expand Up @@ -66,49 +64,14 @@ const SearchJumpTo: React.FC<SearchProps> = ({ isSearchActive }) => {
};
}, [showJumpTo]);

const handleSearchFocus = (e: FocusEvent) => {
if (e.type === "focus") {
setSearchFocus(true);
setShowJumpTo(Boolean(searchValue));
} else {
setSearchFocus(false);
}
};

const handleSearchChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value;
setSearchValue(value);
setShowJumpTo(Boolean(value));
};

useEffect(() => {
setIsLoaded(true);
}, []);

useEffect(() => {
!searchFocus && !searchValue ? isSearchActive(false) : isSearchActive(true);
}, [searchFocus, searchValue, isSearchActive]);
if (!showJumpTo) return null;

return (
<SearchStyled ref={formRef} data-testid="search-jump-to-form">
{/* temporarily setting to textarea for later refinement */}
<textarea
placeholder="Search by keyword or phrase, ex: Berkeley Music Festival"
onChange={handleSearchChange}
onFocus={handleSearchFocus}
onBlur={handleSearchFocus}
ref={search}
name="search"
role="search"
/>
{isLoaded && <IconSearch />}
{showJumpTo && (
<SearchJumpToList
searchValue={searchValue}
setShowJumpTo={setShowJumpTo}
/>
)}
</SearchStyled>
<SearchJumpToList
searchValue={searchValue}
setShowJumpTo={setShowJumpTo}
top={top}
/>
);
};

Expand Down
25 changes: 21 additions & 4 deletions components/Search/JumpToList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe("SearchJumpToList component", () => {
<SearchJumpToList
searchValue="Dylan"
setShowJumpTo={mockSetShowJumpTo}
top={0}
/>,
);
expect(screen.getByTestId("jump-to-wrapper"));
Expand All @@ -31,7 +32,11 @@ describe("SearchJumpToList component", () => {

it("renders Helper components in each JumpTo item", () => {
render(
<SearchJumpToList searchValue="foo" setShowJumpTo={mockSetShowJumpTo} />,
<SearchJumpToList
searchValue="foo"
setShowJumpTo={mockSetShowJumpTo}
top={0}
/>,
);
const helpers = screen.getAllByTestId("helper");
expect(helpers[0]).toHaveTextContent(/in this collection/i);
Expand All @@ -40,7 +45,11 @@ describe("SearchJumpToList component", () => {

it.only("renders route query params in JumpTo items", async () => {
render(
<SearchJumpToList searchValue="foo" setShowJumpTo={mockSetShowJumpTo} />,
<SearchJumpToList
searchValue="foo"
setShowJumpTo={mockSetShowJumpTo}
top={0}
/>,
);

await act(async () => {
Expand All @@ -63,7 +72,11 @@ describe("SearchJumpToList component", () => {
it("selects items correctly on arrow key presses", async () => {
const user = userEvent.setup();
render(
<SearchJumpToList searchValue="foo" setShowJumpTo={mockSetShowJumpTo} />,
<SearchJumpToList
searchValue="foo"
setShowJumpTo={mockSetShowJumpTo}
top={0}
/>,
);
const listItems = await screen.findAllByRole("option");

Expand All @@ -89,7 +102,11 @@ describe("SearchJumpToList component", () => {
it("handles the Escape key press", async () => {
const user = userEvent.setup();
render(
<SearchJumpToList searchValue="foo" setShowJumpTo={mockSetShowJumpTo} />,
<SearchJumpToList
searchValue="foo"
setShowJumpTo={mockSetShowJumpTo}
top={0}
/>,
);

await user.keyboard("{Escape}");
Expand Down
9 changes: 8 additions & 1 deletion components/Search/JumpToList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
JumpItem,
JumpToListStyled,
} from "@/components/Search/JumpTo.styled";

import { IconReturnDownBack } from "@/components/Shared/SVG/Icons";
import Link from "next/link";
import { getCollection } from "@/lib/collection-helpers";
Expand All @@ -13,11 +14,13 @@ import { useRouter } from "next/router";
interface SearchJumpToListProps {
searchValue: string;
setShowJumpTo: Dispatch<React.SetStateAction<boolean>>;
top: number;
}

const SearchJumpToList: React.FC<SearchJumpToListProps> = ({
searchValue,
setShowJumpTo,
top,
}) => {
const router = useRouter();
const [collectionTitle, setCollectionTitle] = useState<string>("");
Expand Down Expand Up @@ -96,7 +99,11 @@ const SearchJumpToList: React.FC<SearchJumpToListProps> = ({
}, [router.query.id]);

return (
<JumpToListStyled data-testid="jump-to-wrapper" role="listbox">
<JumpToListStyled
data-testid="jump-to-wrapper"
role="listbox"
style={{ top }}
>
{jumpToItems.map((item, index) => (
<JumpItem
key={item.dataTestId}
Expand Down
5 changes: 0 additions & 5 deletions components/Search/Search.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const SearchStyled = styled("form", {
display: "flex",
flexShrink: "0",
flexGrow: "1",
marginRight: "$gr4",
transition: "$dcAll",
borderRadius: "3px",
flexWrap: "wrap",
Expand Down Expand Up @@ -42,10 +41,6 @@ const SearchStyled = styled("form", {
flexDirection: "column",
},

"@lg": {
marginRight: "$gr3",
},

"> svg": {
position: "absolute",
display: "flex",
Expand Down
Loading