From afe636e7f3d66506008dc993879ba141ef07584d Mon Sep 17 00:00:00 2001 From: Guillermo Angulo Date: Fri, 2 Jul 2021 18:21:53 -0300 Subject: [PATCH] Add Search component --- .env.development | 3 + package.json | 3 + .../Banner/__snapshots__/test.tsx.snap | 6 +- .../Button/__snapshots__/test.tsx.snap | 4 + src/components/Button/styles.ts | 4 + .../Empty/__snapshots__/test.tsx.snap | 4 + .../FormSignIn/__snapshots__/test.tsx.snap | 4 + .../FormSignUp/__snapshots__/test.tsx.snap | 4 + .../GameCard/__snapshots__/test.tsx.snap | 10 +- .../GameInfo/__snapshots__/test.tsx.snap | 10 +- .../Highlight/__snapshots__/test.tsx.snap | 6 +- src/components/Menu/index.tsx | 6 +- src/components/Menu/test.tsx | 9 +- src/components/Search/Hits/Hit.tsx | 68 ++++++ src/components/Search/Hits/index.tsx | 47 ++++ src/components/Search/Hits/mocks.ts | 28 +++ src/components/Search/Hits/styles.ts | 178 +++++++++++++++ src/components/Search/Hits/test.tsx | 85 +++++++ .../NoResults/__snapshots__/test.tsx.snap | 157 +++++++++++++ src/components/Search/NoResults/index.tsx | 30 +++ src/components/Search/NoResults/styles.ts | 44 ++++ src/components/Search/NoResults/test.tsx | 22 ++ src/components/Search/SearchBox/index.tsx | 56 +++++ src/components/Search/SearchBox/styles.ts | 96 ++++++++ src/components/Search/SearchBox/test.tsx | 55 +++++ src/components/Search/index.tsx | 38 ++++ src/components/Search/stories.tsx | 14 ++ src/components/Search/styles.ts | 54 +++++ src/components/Search/test.tsx | 59 +++++ src/utils/meilisearchClient.ts | 9 + yarn.lock | 212 +++++++++++++++++- 31 files changed, 1308 insertions(+), 17 deletions(-) create mode 100644 src/components/Search/Hits/Hit.tsx create mode 100644 src/components/Search/Hits/index.tsx create mode 100644 src/components/Search/Hits/mocks.ts create mode 100644 src/components/Search/Hits/styles.ts create mode 100644 src/components/Search/Hits/test.tsx create mode 100644 src/components/Search/NoResults/__snapshots__/test.tsx.snap create mode 100644 src/components/Search/NoResults/index.tsx create mode 100644 src/components/Search/NoResults/styles.ts create mode 100644 src/components/Search/NoResults/test.tsx create mode 100644 src/components/Search/SearchBox/index.tsx create mode 100644 src/components/Search/SearchBox/styles.ts create mode 100644 src/components/Search/SearchBox/test.tsx create mode 100644 src/components/Search/index.tsx create mode 100644 src/components/Search/stories.tsx create mode 100644 src/components/Search/styles.ts create mode 100644 src/components/Search/test.tsx create mode 100644 src/utils/meilisearchClient.ts diff --git a/.env.development b/.env.development index 9a53940..9f27fc4 100644 --- a/.env.development +++ b/.env.development @@ -4,3 +4,6 @@ NEXTAUTH_URL=http://localhost:3000 NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY= NEXT_PUBLIC_IMAGE_HOST=http://localhost:1337 NEXT_PUBLIC_GA_TRACKING= + +NEXT_PUBLIC_MEILISEARCH_SERVER=http://localhost:7700 +NEXT_PUBLIC_MEILISEARCH_PUBLIC_KEY= diff --git a/package.json b/package.json index 45f20a5..e16fc46 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ }, "dependencies": { "@apollo/client": "^3.3.20", + "@meilisearch/instant-meilisearch": "^0.5.0", "@stripe/react-stripe-js": "^1.4.1", "@stripe/stripe-js": "^1.15.1", "@styled-icons/boxicons-regular": "^10.34.0", @@ -57,6 +58,7 @@ "polished": "^4.1.3", "react": "17.0.2", "react-dom": "17.0.2", + "react-instantsearch-dom": "^6.11.2", "react-slick": "^0.28.1", "storybook-addon-next-router": "^2.0.4", "styled-components": "5.3.0", @@ -78,6 +80,7 @@ "@types/jest": "^26.0.23", "@types/node": "^15.12.4", "@types/react": "^17.0.11", + "@types/react-instantsearch-dom": "^6.10.1", "@types/react-slick": "^0.23.4", "@types/styled-components": "^5.1.10", "@typescript-eslint/eslint-plugin": "^4.28.0", diff --git a/src/components/Banner/__snapshots__/test.tsx.snap b/src/components/Banner/__snapshots__/test.tsx.snap index 31ecdb5..adf9ef2 100644 --- a/src/components/Banner/__snapshots__/test.tsx.snap +++ b/src/components/Banner/__snapshots__/test.tsx.snap @@ -28,6 +28,10 @@ exports[` should render correctly 1`] = ` padding: 0.8rem 4.8rem; } +.c5 > span { + white-space: nowrap; +} + .c5:focus { box-shadow: 0 0 0 3px #3CD3C1; } @@ -156,4 +160,4 @@ exports[` should render correctly 1`] = ` -`; +` diff --git a/src/components/Button/__snapshots__/test.tsx.snap b/src/components/Button/__snapshots__/test.tsx.snap index 62cb3ef..d8df550 100644 --- a/src/components/Button/__snapshots__/test.tsx.snap +++ b/src/components/Button/__snapshots__/test.tsx.snap @@ -28,6 +28,10 @@ exports[` -`; +` diff --git a/src/components/Highlight/__snapshots__/test.tsx.snap b/src/components/Highlight/__snapshots__/test.tsx.snap index fbf7174..98b53b3 100644 --- a/src/components/Highlight/__snapshots__/test.tsx.snap +++ b/src/components/Highlight/__snapshots__/test.tsx.snap @@ -28,6 +28,10 @@ exports[` should render headings and button 1`] = ` padding: 0.8rem 3.2rem; } +.c5 > span { + white-space: nowrap; +} + .c5:focus { box-shadow: 0 0 0 3px #3CD3C1; } @@ -139,4 +143,4 @@ exports[` should render headings and button 1`] = ` -`; +` diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx index 1e19890..7effbc6 100644 --- a/src/components/Menu/index.tsx +++ b/src/components/Menu/index.tsx @@ -2,7 +2,6 @@ import Link from 'next/link' import { useState } from 'react' import { Menu2 as MenuIcon } from '@styled-icons/remix-fill/Menu2' -import { Search as SearchIcon } from '@styled-icons/material-outlined/Search' import { Close as CloseIcon } from '@styled-icons/material-outlined/Close' import Button from 'components/Button' @@ -12,6 +11,7 @@ import * as S from './styles' import CartDropdown from 'components/CartDropdown' import CartIcon from 'components/CartIcon' import UserDropdown from 'components/UserDropdown' +import Search from 'components/Search' export type MenuProps = { username?: string | null @@ -51,9 +51,7 @@ const Menu = ({ username, loading }: MenuProps) => { {!loading && ( <> - - - + diff --git a/src/components/Menu/test.tsx b/src/components/Menu/test.tsx index b112db2..9802282 100644 --- a/src/components/Menu/test.tsx +++ b/src/components/Menu/test.tsx @@ -9,13 +9,20 @@ useRouter.mockImplementation(() => ({ query: {} })) +jest.mock('components/Search', () => ({ + __esModule: true, + default: function Mock() { + return
+ } +})) + describe('', () => { it('should render the menu', () => { render() expect(screen.getByLabelText(/open menu/i)).toBeInTheDocument() expect(screen.getByRole('img', { name: /won games/i })).toBeInTheDocument() - expect(screen.getByLabelText(/search/i)).toBeInTheDocument() + expect(screen.getByTestId(/mock search/i)).toBeInTheDocument() expect(screen.getAllByLabelText(/shopping cart/i)).toHaveLength(2) }) diff --git a/src/components/Search/Hits/Hit.tsx b/src/components/Search/Hits/Hit.tsx new file mode 100644 index 0000000..c5ad2ac --- /dev/null +++ b/src/components/Search/Hits/Hit.tsx @@ -0,0 +1,68 @@ +import Image from 'next/image' +import Link from 'next/link' + +import { Highlight } from 'react-instantsearch-dom' +import { getImageUrl } from 'utils/getImageUrl' +import formatPrice from 'utils/format-price' + +import { Apple, Windows, Linux } from '@styled-icons/fa-brands' +import * as S from './styles' + +import { GameHitProps, Platform } from '.' + +export type HitProps = { + hit: GameHitProps +} + +const Hit = ({ hit }: HitProps) => { + const platformIcons = { + linux: , + mac: , + windows: + } + const releaseYear = + hit.release_date && new Date(hit.release_date).getFullYear() + + return ( + + + + {hit.name} + + + + + {releaseYear && ( + + {releaseYear} + + )} + + + {formatPrice(hit.price)} + + {hit.platforms.map((icon: Platform) => ( + + {platformIcons[icon.name]} + + ))} + + + + + + + + + ) +} + +export default Hit diff --git a/src/components/Search/Hits/index.tsx b/src/components/Search/Hits/index.tsx new file mode 100644 index 0000000..50cec33 --- /dev/null +++ b/src/components/Search/Hits/index.tsx @@ -0,0 +1,47 @@ +import { connectHits, connectStateResults } from 'react-instantsearch-dom' + +import NoResults from '../NoResults' +import Hit from './Hit' + +import * as S from './styles' + +export type Platform = { + name: 'windows' | 'linux' | 'mac' +} + +export type Cover = { + url: string +} + +export type GameHitProps = { + id: string + name: string + short_description: string + cover: Cover | null + slug: string + price: number + platforms: Platform[] + release_date: string | null +} + +export type HitsProps = { + hits: GameHitProps[] +} + +const Hits = connectHits(({ hits }: HitsProps) => ( + + {hits.length ? ( + hits.map((hit) => ( + + + + )) + ) : ( + + )} + +)) + +export default connectStateResults(({ searchState }) => + searchState?.query ? : null +) diff --git a/src/components/Search/Hits/mocks.ts b/src/components/Search/Hits/mocks.ts new file mode 100644 index 0000000..0d319f1 --- /dev/null +++ b/src/components/Search/Hits/mocks.ts @@ -0,0 +1,28 @@ +import { GameHitProps } from '.' + +export default [ + { + id: '1', + name: 'Game 1', + short_description: 'Description', + cover: { + url: '/cover-1.jpg' + }, + slug: 'game-1', + price: 25, + platforms: [{ name: 'windows' }], + release_date: '2015-12-05' + }, + { + id: '2', + name: 'Game 2', + short_description: 'Description', + cover: { + url: '/cover-2.jpg' + }, + slug: 'game-2', + price: 12, + platforms: [{ name: 'linux' }], + release_date: '2006-10-30' + } +] as GameHitProps[] diff --git a/src/components/Search/Hits/styles.ts b/src/components/Search/Hits/styles.ts new file mode 100644 index 0000000..eab6bcb --- /dev/null +++ b/src/components/Search/Hits/styles.ts @@ -0,0 +1,178 @@ +import styled, { css } from 'styled-components' +import media from 'styled-media-query' + +export const List = styled.ul` + ${({ theme }) => css` + position: absolute; + width: 100%; + max-height: 54rem; + margin-top: ${theme.spacings.xxsmall}; + background: ${theme.colors.lightBg}; + box-shadow: 0 0.2rem 1.6rem 0.1rem ${theme.colors.black}; + overflow-y: auto; + overflow-x: hidden; + list-style: none; + z-index: ${theme.layers.alwaysOnTop}; + + li:not(:last-child) { + border-bottom: 0.1rem solid ${theme.colors.lightGray}; + } + + ${media.lessThan('large')` + position: fixed; + max-height: calc(100vh - (7rem + 4.2rem + ${theme.spacings.xxsmall} + ${theme.spacings.small})); //Sticky Note + Input height + Menu padding-top + List margin-top + right: 0; + left: 0; + `} + `} +` + +export const ListItem = styled.li` + ${({ theme }) => css` + padding: 0.2rem ${theme.spacings.xxsmall}; + + &:first-child { + padding-top: ${theme.spacings.xxsmall}; + } + &:last-child { + padding-bottom: ${theme.spacings.xxsmall}; + } + `} +` + +export const Result = styled.a` + ${({ theme }) => css` + display: flex; + padding: 0.6rem ${theme.spacings.xxsmall}; + background: ${theme.colors.lightBg}; + color: ${theme.colors.darkGray}; + height: 10rem; + font-size: ${theme.font.sizes.small}; + cursor: pointer; + text-decoration: none; + border-radius: ${theme.border.radius}; + transition: background ${theme.transition.fast}, + color ${theme.transition.fast}; + + &:hover { + color: ${theme.colors.white}; + background: ${theme.colors.primary}; + + * > time { + color: ${theme.colors.lightGray}; + } + } + + ${media.lessThan('medium')` + font-size: ${theme.font.sizes.xsmall}; + `} + `} +` + +export const ImageWrapper = styled.div` + position: relative; + min-width: 22rem; + + img { + max-width: 100%; + max-height: 100%; + } + + ${media.lessThan('medium')` + min-width: 12rem; + height: 6rem; + align-self: center; + `} + + ${media.lessThan('small')` + display: none; + `} +` + +export const Info = styled.div` + ${({ theme }) => css` + align-self: center; + padding: 0 ${theme.spacings.xsmall}; + overflow: hidden; + + em { + font-style: normal; + font-weight: ${theme.font.bold}; + color: ${theme.colors.black}; + background: #fdf39a; + border-radius: 0.2rem; + } + + ${media.lessThan('small')` + padding: 0; + `} + `} +` + +export const Title = styled.h3` + ${({ theme }) => css` + font-size: ${theme.font.sizes.medium}; + font-weight: ${theme.font.bold}; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + + ${media.lessThan('medium')` + font-size: ${theme.font.sizes.small}; + `} + `} +` + +export const ReleaseYear = styled.time` + ${({ theme }) => css` + font-size: ${theme.font.sizes.xsmall}; + font-weight: ${theme.font.bold}; + color: ${theme.colors.gray}; + margin: 0 ${theme.spacings.xxsmall}; + transition: color ${theme.transition.fast}; + `} +` + +export const Details = styled.div` + ${({ theme }) => css` + display: flex; + gap: ${theme.spacings.xxsmall}; + `} +` + +export const Price = styled.strong` + ${({ theme }) => css` + display: inline-flex; + font-weight: ${theme.font.bold}; + padding: 0 0.4rem; + border-radius: ${theme.border.radius}; + background-color: ${theme.colors.secondary}; + color: ${theme.colors.white}; + `} +` + +export const Platform = styled.div` + ${({ theme }) => css` + display: flex; + align-items: center; + justify-content: center; + gap: ${theme.spacings.xxsmall}; + `} +` + +export const PlatformIcon = styled.span` + ${({ theme }) => css` + & > svg { + fill: ${theme.colors.lightGray}; + width: 1.4rem; + height: 1.4rem; + } + `} +` + +export const Description = styled.p` + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +` diff --git a/src/components/Search/Hits/test.tsx b/src/components/Search/Hits/test.tsx new file mode 100644 index 0000000..4673c90 --- /dev/null +++ b/src/components/Search/Hits/test.tsx @@ -0,0 +1,85 @@ +import { render, screen } from 'utils/test-utils' +import Hits, { GameHitProps } from '.' +import Hit from './Hit' +import mockedHits from './mocks' + +jest.mock('components/Search/NoResults', () => ({ + __esModule: true, + default: function Mock() { + return
+ } +})) + +let mockQuery = '' +let mockHits: GameHitProps[] = [] + +jest.mock('react-instantsearch-dom', () => ({ + Highlight: function Mock({ attribute }: { attribute: string }) { + return {attribute} + }, + connectStateResults: jest.fn().mockImplementation( + (component) => () => + component({ + searching: false, + searchState: { + query: mockQuery + } + }) + ), + connectHits: jest.fn().mockImplementation( + (component) => () => + component({ + hits: mockHits + }) + ) +})) + +describe('', () => { + it('should not render when there is no query', () => { + mockQuery = '' + + render() + + expect(screen.queryByRole('list')).not.toBeInTheDocument() + }) + + it('should render NoResults when there is a query but no hits', () => { + mockQuery = 'query' + mockHits = [] + + render() + + expect(screen.getByTestId(/mock noresults/i)).toBeInTheDocument() + }) + + it('should render hits in the results list', () => { + mockHits = mockedHits + + render() + + expect(screen.getAllByRole('listitem')).toHaveLength(2) + }) +}) + +describe('', () => { + it('should render properly', () => { + render() + + expect(screen.getByRole('link')).toHaveAttribute( + 'href', + `/game/${mockedHits[0].slug}` + ) + expect(screen.getByText(/\$25\.00/)).toBeInTheDocument() + expect(screen.getByText(/2015/)).toBeInTheDocument() // Release year + expect(screen.getByRole('img', { name: /windows/i })).toBeInTheDocument() + }) + + it('should render highlighting name and short_description', () => { + render() + + const highlights = screen.getAllByTestId(/mock highlight/i) + + expect(highlights[0]).toHaveTextContent('name') + expect(highlights[1]).toHaveTextContent('short_description') + }) +}) diff --git a/src/components/Search/NoResults/__snapshots__/test.tsx.snap b/src/components/Search/NoResults/__snapshots__/test.tsx.snap new file mode 100644 index 0000000..3fa7c49 --- /dev/null +++ b/src/components/Search/NoResults/__snapshots__/test.tsx.snap @@ -0,0 +1,157 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render correctly 1`] = ` +.c5 { + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + background: linear-gradient(180deg,#ff5f5f 0%,#f062c0 50%); + color: #FAFAFA; + font-family: Poppins,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif; + border: 0; + cursor: pointer; + border-radius: 0.4rem; + padding: 0.8rem; + -webkit-text-decoration: none; + text-decoration: none; + height: 4rem; + font-size: 1.4rem; + padding: 0.8rem 3.2rem; +} + +.c5 > span { + white-space: nowrap; +} + +.c5:focus { + box-shadow: 0 0 0 3px #3CD3C1; +} + +.c5:hover { + background: linear-gradient(180deg,#e35565 0%,#d958a6 50%); +} + +.c3 { + display: inline-block; + vertical-align: middle; + overflow: hidden; +} + +.c0 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + height: calc(10rem + (0.8rem * 2)); +} + +.c1 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + padding: 0 2.4rem; +} + +.c2 { + font-weight: 600; + font-size: 1.8rem; +} + +.c4 { + font-size: 1.6rem; +} + +@media (max-width:768px) { + .c0 img { + display: none; + } +} + +@media (max-width:768px) { + .c2 { + font-size: 1.6rem; + } +} + +@media (max-width:768px) { + .c4 { + font-size: 1.4rem; + } +} + + +
+
  • + A gamer in a couch playing videogame +
    +

    + + No Results Found +

    +

    + Try searching another term. +

    + + + Explore all games + + +
    +
  • +
    + +` diff --git a/src/components/Search/NoResults/index.tsx b/src/components/Search/NoResults/index.tsx new file mode 100644 index 0000000..9a40a0d --- /dev/null +++ b/src/components/Search/NoResults/index.tsx @@ -0,0 +1,30 @@ +import Image from 'next/image' +import Link from 'next/link' + +import Button from 'components/Button' + +import { Search as SearchIcon } from '@styled-icons/material-outlined' +import * as S from './styles' + +const NoResults = () => ( + + A gamer in a couch playing videogame + + + + No Results Found + + Try searching another term. + + + + + +) + +export default NoResults diff --git a/src/components/Search/NoResults/styles.ts b/src/components/Search/NoResults/styles.ts new file mode 100644 index 0000000..1ade9b4 --- /dev/null +++ b/src/components/Search/NoResults/styles.ts @@ -0,0 +1,44 @@ +import styled, { css } from 'styled-components' +import media from 'styled-media-query' + +export const Wrapper = styled.li` + ${({ theme }) => css` + display: flex; + align-items: center; + justify-content: center; + height: calc(10rem + (${theme.spacings.xxsmall} * 2)); + + ${media.lessThan('medium')` + img { + display: none; + } + `} + `} +` + +export const Info = styled.div` + ${({ theme }) => css` + display: flex; + flex-direction: column; + padding: 0 ${theme.spacings.small}; + `} +` + +export const Title = styled.h3` + ${({ theme }) => css` + font-weight: ${theme.font.bold}; + font-size: ${theme.font.sizes.large}; + ${media.lessThan('medium')` + font-size: ${theme.font.sizes.medium}; + `} + `} +` + +export const Description = styled.p` + ${({ theme }) => css` + font-size: ${theme.font.sizes.medium}; + ${media.lessThan('medium')` + font-size: ${theme.font.sizes.small}; + `} + `} +` diff --git a/src/components/Search/NoResults/test.tsx b/src/components/Search/NoResults/test.tsx new file mode 100644 index 0000000..8f2043a --- /dev/null +++ b/src/components/Search/NoResults/test.tsx @@ -0,0 +1,22 @@ +import { render, screen } from 'utils/test-utils' +import NoResults from '.' + +describe('', () => { + it('should render correctly', () => { + const { container } = render() + + expect(screen.getByRole('img')).toBeInTheDocument() + + expect( + screen.getByRole('heading', { name: /no results found/i }) + ).toBeInTheDocument() + + expect(screen.getByText(/try searching another term/i)).toBeInTheDocument() + + expect( + screen.getByRole('link', { name: /explore all games/i }) + ).toHaveAttribute('href', '/games') + + expect(container.parentElement).toMatchSnapshot() + }) +}) diff --git a/src/components/Search/SearchBox/index.tsx b/src/components/Search/SearchBox/index.tsx new file mode 100644 index 0000000..ed05ff8 --- /dev/null +++ b/src/components/Search/SearchBox/index.tsx @@ -0,0 +1,56 @@ +import { useRef, useEffect } from 'react' +import { connectSearchBox } from 'react-instantsearch-dom' + +import { + Search as SearchIcon, + Close as CloseIcon +} from '@styled-icons/material-outlined' +import * as S from './styles' + +export type SearchBoxProps = { + handleVisibility?: () => void + isOpen: boolean + currentRefinement: string + refine: (v: string) => void +} + +const SearchBox = connectSearchBox( + ({ handleVisibility, isOpen, currentRefinement, refine }: SearchBoxProps) => { + const inputRef = useRef(null) + + useEffect(() => { + if (isOpen && inputRef.current) { + inputRef.current.focus() + } + }, [isOpen]) + + return ( + + + refine(event.currentTarget.value)} + disabled={!isOpen} + /> + + + {isOpen ? ( + + ) : ( + + )} + + + + ) + } +) + +export default SearchBox diff --git a/src/components/Search/SearchBox/styles.ts b/src/components/Search/SearchBox/styles.ts new file mode 100644 index 0000000..a1f73a2 --- /dev/null +++ b/src/components/Search/SearchBox/styles.ts @@ -0,0 +1,96 @@ +import styled, { css, DefaultTheme } from 'styled-components' +import { darken } from 'polished' + +export const SearchForm = styled.form` + display: flex; + justify-content: flex-end; +` + +export const Input = styled.input` + ${({ theme }) => css` + color: ${theme.colors.white}; + font-family: ${theme.font.family}; + font-size: ${theme.font.sizes.medium}; + padding: ${theme.spacings.xxsmall} 0; + padding-right: ${theme.spacings.small}; + background: transparent; + border: 0; + outline: none; + + &::placeholder { + color: ${theme.colors.white}; + } + + &::-webkit-search-cancel-button { + -webkit-appearance: none; + } + `} +` + +const inputModifiers = { + open: (theme: DefaultTheme) => css` + background: ${theme.colors.darkGray}; + border-color: ${theme.colors.white}; + width: 100%; + ${Input} { + transition: opacity ${theme.transition.default} 0.2s; + width: 100%; + opacity: 1; + pointer-events: auto; + visibility: visible; + } + `, + close: (theme: DefaultTheme) => css` + background: none; + border-color: transparent; + width: 0; + ${Input} { + transition: opacity ${theme.transition.fast}; + width: 0; + opacity: 0; + pointer-events: none; + visibility: hidden; + } + ` +} + +type InputWrapperProps = { + isOpen?: boolean +} + +export const InputWrapper = styled.div` + ${({ theme, isOpen }) => css` + position: relative; + max-height: 4.2rem; + display: flex; + align-items: center; + justify-content: flex-end; + padding: 0 ${theme.spacings.xsmall}; + border-radius: ${theme.border.radius}; + z-index: ${theme.layers.alwaysOnTop}; + border: 0.1rem solid; + transition: width ${theme.transition.default}, + border-color ${theme.transition.fast} 0.1s, + background ${theme.transition.fast} 0.1s; + + ${isOpen && inputModifiers.open(theme)} + ${!isOpen && inputModifiers.close(theme)} + `} +` + +export const Icon = styled.div` + ${({ theme }) => css` + color: ${theme.colors.white}; + cursor: pointer; + transition: color ${theme.transition.default}; + + &:hover { + color: ${darken(0.3, theme.colors.white)}; + } + + & > svg { + width: 2.4rem; + height: 2.4rem; + } + `} +` diff --git a/src/components/Search/SearchBox/test.tsx b/src/components/Search/SearchBox/test.tsx new file mode 100644 index 0000000..f1f62bf --- /dev/null +++ b/src/components/Search/SearchBox/test.tsx @@ -0,0 +1,55 @@ +import userEvent from '@testing-library/user-event' +import { render, screen, waitFor } from 'utils/test-utils' +import SearchBox, { SearchBoxProps } from '.' + +const mockRefine = jest.fn() + +jest.mock('react-instantsearch-dom', () => ({ + connectSearchBox: jest.fn().mockImplementation( + (component) => (props: SearchBoxProps) => + component({ + ...props, + currentRefinement: '', + refine: mockRefine + }) + ) +})) + +describe('', () => { + it('should render correctly when is closed', () => { + render() + + const input = screen.getByPlaceholderText(/search here…/i) + + expect(input).toBeDisabled() + expect(input).toHaveStyle({ width: '0', opacity: '0' }) + + expect(screen.getByLabelText(/open search/i)).toBeInTheDocument() + expect(screen.queryByLabelText(/close search/i)).not.toBeInTheDocument() + }) + + it('should render correctly when is open', () => { + render() + + const input = screen.getByRole('searchbox') + + expect(input).toHaveFocus() + expect(input).toHaveStyle({ width: '100%', opacity: '1' }) + + expect(screen.getByLabelText(/close search/i)).toBeInTheDocument() + expect(screen.queryByLabelText(/open search/i)).not.toBeInTheDocument() + }) + + it('should call refine method every time the user types', async () => { + render() + + const input = screen.getByRole('searchbox') + const text = 'game' + + userEvent.type(input, text) + + await waitFor(() => { + expect(mockRefine).toHaveBeenCalledTimes(text.length) + }) + }) +}) diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx new file mode 100644 index 0000000..e5d9dd7 --- /dev/null +++ b/src/components/Search/index.tsx @@ -0,0 +1,38 @@ +import { useEffect, useState } from 'react' + +import { searchClient } from 'utils/meilisearchClient' +import { InstantSearch, Configure } from 'react-instantsearch-dom' + +import SearchBox from './SearchBox' +import Hits from './Hits' + +import * as S from './styles' + +const Search = () => { + const [isOpen, setIsOpen] = useState(false) + + useEffect(() => { + document.body.style.overflow = isOpen ? 'hidden' : 'unset' + + return () => { + document.body.style.overflow = 'unset' + } + }, [isOpen]) + + return ( + + + setIsOpen(!isOpen)} + isOpen={isOpen} + /> + {/** Maximum number of results */} + + + + setIsOpen(!isOpen)} aria-hidden={!isOpen} /> + + ) +} + +export default Search diff --git a/src/components/Search/stories.tsx b/src/components/Search/stories.tsx new file mode 100644 index 0000000..5eec90c --- /dev/null +++ b/src/components/Search/stories.tsx @@ -0,0 +1,14 @@ +import { Story, Meta } from '@storybook/react/types-6-0' +import Search from '.' + +export default { + title: 'Search', + component: Search, + parameters: { + backgrounds: { + default: 'won-dark' + } + } +} as Meta + +export const Default: Story = () => diff --git a/src/components/Search/styles.ts b/src/components/Search/styles.ts new file mode 100644 index 0000000..1826ce6 --- /dev/null +++ b/src/components/Search/styles.ts @@ -0,0 +1,54 @@ +import styled, { css } from 'styled-components' +import { List } from './Hits/styles' + +export const Overlay = styled.div` + ${({ theme }) => css` + background: rgba(0, 0, 0, 0.5); + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: ${theme.layers.overlay}; + `} +` + +type WrapperProps = { + isOpen?: boolean +} + +const wrapperModifiers = { + open: () => css` + opacity: 1; + pointer-events: auto; + visibility: visible; + `, + close: () => css` + opacity: 0; + pointer-events: none; + visibility: hidden; + ` +} + +export const Wrapper = styled.div` + ${({ theme, isOpen }) => css` + position: relative; + width: 100%; + + ${Overlay} { + transition: opacity ${theme.transition.default}; + ${isOpen && wrapperModifiers.open()} + ${!isOpen && wrapperModifiers.close()} + } + + ${List} { + transform-origin: top center; + transition: transform 0.3s ease-in, opacity ${theme.transition.default}; + transform: ${isOpen + ? 'translateY(0)' + : `translateY(-${theme.spacings.xsmall})`}; + ${isOpen && wrapperModifiers.open()} + ${!isOpen && wrapperModifiers.close()} + } + `} +` diff --git a/src/components/Search/test.tsx b/src/components/Search/test.tsx new file mode 100644 index 0000000..d0153ff --- /dev/null +++ b/src/components/Search/test.tsx @@ -0,0 +1,59 @@ +import userEvent from '@testing-library/user-event' +import { render, screen } from 'utils/test-utils' + +import Search from '.' +import { SearchBoxProps } from './SearchBox' + +jest.mock('next/link', () => ({ + __esModule: true, + default: function Mock({ children }: { children: React.ReactNode }) { + return
    {children}
    + } +})) + +jest.mock('./Hits', () => ({ + __esModule: true, + default: function Mock() { + return
    + } +})) + +jest.mock('utils/meilisearchClient', () => ({ + searchClient: jest.fn().mockResolvedValue({}) +})) + +jest.mock('react-instantsearch-dom', () => ({ + InstantSearch: function Mock({ children }: { children: React.ReactNode }) { + return
    {children}
    + }, + Configure: jest.fn().mockImplementation(() => null), + connectSearchBox: jest.fn().mockImplementation( + (component) => (props: SearchBoxProps) => + component({ + ...props, + currentRefinement: '', + refine: jest.fn() + }) + ) +})) + +describe('', () => { + it('should hide input when clicking on overlay', () => { + render() + + const overlay = screen.getByTestId(/mock instantsearch/i).nextElementSibling + + userEvent.click(screen.getByLabelText(/open search/i)) + + expect(overlay).toHaveStyle({ opacity: 1 }) + expect(overlay!.getAttribute('aria-hidden')).toBe('false') + + userEvent.click(overlay!) + + expect(screen.getByPlaceholderText(/search here…/i)).toHaveStyle({ + opacity: 0 + }) + expect(overlay).toHaveStyle({ opacity: 0 }) + expect(overlay!.getAttribute('aria-hidden')).toBe('true') + }) +}) diff --git a/src/utils/meilisearchClient.ts b/src/utils/meilisearchClient.ts new file mode 100644 index 0000000..65a2a22 --- /dev/null +++ b/src/utils/meilisearchClient.ts @@ -0,0 +1,9 @@ +import { instantMeiliSearch } from '@meilisearch/instant-meilisearch' + +if (!process.env.NEXT_PUBLIC_MEILISEARCH_SERVER) + throw new Error('Missing env.NEXT_PUBLIC_MEILISEARCH_SERVER') + +const server = process.env.NEXT_PUBLIC_MEILISEARCH_SERVER +const key = process.env.NEXT_PUBLIC_MEILISEARCH_PUBLIC_KEY ?? '' + +export const searchClient = instantMeiliSearch(server, key) diff --git a/yarn.lock b/yarn.lock index 1261349..6c58414 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,110 @@ # yarn lockfile v1 +"@algolia/cache-browser-local-storage@4.10.2": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.10.2.tgz#9925c7c0ce94257564b8948b60fc427c4a98124c" + integrity sha512-B3NInwobEAim4J4Y0mgZermoi0DCXdTT/Q+4ehLamqUqxLw8To5zc9izjg7B8JaFSQsqflRdCeRmYEv2gYDY7g== + dependencies: + "@algolia/cache-common" "4.10.2" + +"@algolia/cache-common@4.10.2": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.10.2.tgz#0113419518419895118d132bed4115345a865ce3" + integrity sha512-xcGbV0+6gLu2C7XoJdD+Pp6wWjROle6PNDsa6O21vS7fw1a03xb2bEnFdl1U31bs69P1z8IRy3h+8RVBouvhhw== + +"@algolia/cache-in-memory@4.10.2": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.10.2.tgz#2d34d4155425b385d19ff197a8943a4b5084c790" + integrity sha512-zPIcxHQEJXy+M35A+v9Y5u5BAQOKR2aFK0kYpAdW/OrgxYcrFHtVCxwIWB/ZhGbkDtzCW8/8tJeddcD5YsHX9Q== + dependencies: + "@algolia/cache-common" "4.10.2" + +"@algolia/client-account@4.10.2": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.10.2.tgz#c53d18d4f57ab5343c21e0ed795421964ba0cbb9" + integrity sha512-iuIU+xUtjgR9p4Hpujlr8mePDPSrVIk3peg+RAUhxniLBDaI+OhgHyhP6Lmh9flWk+JfRg91Rhk46xuxMLqwfA== + dependencies: + "@algolia/client-common" "4.10.2" + "@algolia/client-search" "4.10.2" + "@algolia/transporter" "4.10.2" + +"@algolia/client-analytics@4.10.2": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.10.2.tgz#93c881cfb9e5df389725d821327fa801f1baa2c6" + integrity sha512-u47J65NHs0fMryDrMeuLMGjXDOKt/muF9WlfbMslT2Cvdd7PZwl9KYnT7xMhnmBB8TDiDMmEQkDykhnCOnwVNw== + dependencies: + "@algolia/client-common" "4.10.2" + "@algolia/client-search" "4.10.2" + "@algolia/requester-common" "4.10.2" + "@algolia/transporter" "4.10.2" + +"@algolia/client-common@4.10.2": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.10.2.tgz#a715e8feb2a2b6ea38765f53e8ae6ffc4ed80aba" + integrity sha512-sfgZCv9ha9aHbe3ErAYb1blg2qx4XTLvQqP1jq8asU75rrH9XBTtSzQQO43GlArwhtwCHLgcWquN3WgPlLzkiQ== + dependencies: + "@algolia/requester-common" "4.10.2" + "@algolia/transporter" "4.10.2" + +"@algolia/client-personalization@4.10.2": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.10.2.tgz#89d761bcf60ce13b8565c2ae8ab644c3a3d114c8" + integrity sha512-2UhUNo/czfA/keOC3+vFyMnFGV/E1Zkm+ek9Fsk/9miS39UMhx2CmH5vKSIJ7jxLSin7zBaCwKt65phfYty1pg== + dependencies: + "@algolia/client-common" "4.10.2" + "@algolia/requester-common" "4.10.2" + "@algolia/transporter" "4.10.2" + +"@algolia/client-search@4.10.2": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.10.2.tgz#ad281b04ec4e6eaff68fb5be330f0bdf965ce011" + integrity sha512-ZdOh6XS6Y9bcekfG4y0VhdoIYfsTounsgXX4Bt3X2RCcmY3uotgaq2EVY58E6q6nvfgBfPHW18+AZCHKTWHAAw== + dependencies: + "@algolia/client-common" "4.10.2" + "@algolia/requester-common" "4.10.2" + "@algolia/transporter" "4.10.2" + +"@algolia/logger-common@4.10.2": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.10.2.tgz#f28e966a6b878af2917ed2e1518f46650a6fb8ad" + integrity sha512-UJaU6arzmW+FT5fCv5NIbxNMtEoGcf+UENmZxxu7k7UWPARR2XL4ljJ45Jv14Z5dlz32LXWtR1PRmNfkDMk22Q== + +"@algolia/logger-console@4.10.2": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.10.2.tgz#9d3dcbb077242db92f0f0a1795ec95c3bc839599" + integrity sha512-JrCrZ7CGs/TsyNR2AWe9Vdd6rsuxfvfcpqbu+CY7LBUYEnV8GERkf7FnDNaKVNsFJqClILCGh3U8CzQ1G5L+kA== + dependencies: + "@algolia/logger-common" "4.10.2" + +"@algolia/requester-browser-xhr@4.10.2": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.10.2.tgz#2286e2f10fff3651f719b8d7d3defc8c032fcce0" + integrity sha512-LveaAp7/oCBotv1aZ4VHz8fCcJA7v/28ayh+Ljlm+hYXsxgs6NAYKz7iBpxGN7q5MV8GM+MThRYNFoT0cHTMxQ== + dependencies: + "@algolia/requester-common" "4.10.2" + +"@algolia/requester-common@4.10.2": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.10.2.tgz#8b62f0848454ec5b07bd3599f5fb2b87ec7c4de8" + integrity sha512-3J2W0fAaURLGK0lEGeNb8eWJnQcsu+oIcfJTCIYkYT5T9w21M65kUUyD9QSf/K137qQts3tzGniUR3LxfovlXA== + +"@algolia/requester-node-http@4.10.2": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.10.2.tgz#edb691e34e18aacc15107193319e1a712e024649" + integrity sha512-IBqsalCGgn0CrOP1PKRB5rufEOvHlrSQUFEGXZ8mxmE/zU8CLX2LKqdHbEFeNDLFl+l+8HW5BGVDGD2rvG+hSg== + dependencies: + "@algolia/requester-common" "4.10.2" + +"@algolia/transporter@4.10.2": + version "4.10.2" + resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.10.2.tgz#ae0fa7c99b9bf8efa5ac83843558be1074e7c045" + integrity sha512-I3QDRSookQtPSUEnxT2XCShhipCT4beJBpWhteNwMrWQF/SqTsveqSR6bX0G49lDh9MOmYrOlCegteuKuT/tEw== + dependencies: + "@algolia/cache-common" "4.10.2" + "@algolia/logger-common" "4.10.2" + "@algolia/requester-common" "4.10.2" + "@apollo/client@^3.3.20": version "3.3.20" resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.3.20.tgz#8f0935fa991857e9cf2e73c9bd378ad7ec97caf8" @@ -1611,6 +1715,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.1.2", "@babel/runtime@^7.14.0", "@babel/runtime@^7.14.6": + version "7.14.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d" + integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.10.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.10.3" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.3.tgz#670d002655a7c366540c67f6fd3342cd09500364" @@ -1625,13 +1736,6 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.14.0", "@babel/runtime@^7.14.6": - version "7.14.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d" - integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/template@^7.10.4", "@babel/template@^7.3.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" @@ -2339,6 +2443,13 @@ resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b" integrity sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA== +"@meilisearch/instant-meilisearch@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@meilisearch/instant-meilisearch/-/instant-meilisearch-0.5.0.tgz#dbad2ff0f7e6ced2cb8ecc176e7b7e82ee34f6b8" + integrity sha512-f2pocX10r8MR+dNr4pN7AoUjjGV30hZqBojGiRZRaVkcSm9+TPJ6fDljo1mp6CPMIi6ja3pC/ALSEpAgPINAWg== + dependencies: + meilisearch "^0.18.1" + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -3964,6 +4075,23 @@ dependencies: "@types/react" "*" +"@types/react-instantsearch-core@*": + version "6.10.2" + resolved "https://registry.yarnpkg.com/@types/react-instantsearch-core/-/react-instantsearch-core-6.10.2.tgz#634a887233ce76cc0f37a37f30909fe77a24d73b" + integrity sha512-dG/XHdrPWjVvQTTOg4Q5somVfE6xePOEFJXVeVsRNB+Pj8tzfFR6niFOStf791wGM9BKVBmmy2rCAMhcbfROnw== + dependencies: + "@types/react" "*" + algoliasearch ">=4" + algoliasearch-helper ">=3" + +"@types/react-instantsearch-dom@^6.10.1": + version "6.10.1" + resolved "https://registry.yarnpkg.com/@types/react-instantsearch-dom/-/react-instantsearch-dom-6.10.1.tgz#9a0aa032c18e38c429f0d2a7c432959beb45f5a7" + integrity sha512-LISZFa3NHTB8455e+5q/igQVt11ElnUQcYp1/O+ju46Suck83EjyS4YXacUx+zTJGlagIxJadGMV+V7amJAzTQ== + dependencies: + "@types/react" "*" + "@types/react-instantsearch-core" "*" + "@types/react-slick@^0.23.4": version "0.23.4" resolved "https://registry.yarnpkg.com/@types/react-slick/-/react-slick-0.23.4.tgz#c97e2a9e7e3d1933c68593b8e82752fab1e8ce53" @@ -4623,6 +4751,33 @@ ajv@^8.0.1: require-from-string "^2.0.2" uri-js "^4.2.2" +algoliasearch-helper@>=3, algoliasearch-helper@^3.4.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.5.3.tgz#fbf8b328bc103efdefde59a7d25eaffe85b2490f" + integrity sha512-DtSlOKAJ6TGkQD6u58g6/ABdMmHf3pAj6xVL5hJF+D4z9ldDRf/f5v6puNIxGOlJRwGVvFGyz34beYNqhLDUbQ== + dependencies: + events "^1.1.1" + +algoliasearch@>=4: + version "4.10.2" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.10.2.tgz#23e88c71cb381d5b59430baa5d417186cc8ff683" + integrity sha512-BAYCe97XRfO15irJKBRjBnrp9tSqN0jppklLIXKdtUcXlibcPQtuAeGUP2cPiz6bJd3ISuoYzLFNt4/fQYtLMw== + dependencies: + "@algolia/cache-browser-local-storage" "4.10.2" + "@algolia/cache-common" "4.10.2" + "@algolia/cache-in-memory" "4.10.2" + "@algolia/client-account" "4.10.2" + "@algolia/client-analytics" "4.10.2" + "@algolia/client-common" "4.10.2" + "@algolia/client-personalization" "4.10.2" + "@algolia/client-search" "4.10.2" + "@algolia/logger-common" "4.10.2" + "@algolia/logger-console" "4.10.2" + "@algolia/requester-browser-xhr" "4.10.2" + "@algolia/requester-common" "4.10.2" + "@algolia/requester-node-http" "4.10.2" + "@algolia/transporter" "4.10.2" + anser@1.4.9: version "1.4.9" resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.9.tgz#1f85423a5dcf8da4631a341665ff675b96845760" @@ -6891,6 +7046,13 @@ create-react-context@0.3.0: gud "^1.0.0" warning "^4.0.3" +cross-fetch@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" + integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ== + dependencies: + node-fetch "2.6.1" + cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -8263,6 +8425,11 @@ eventemitter2@^6.4.3: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.4.tgz#aa96e8275c4dbeb017a5d0e03780c65612a1202b" integrity sha512-HLU3NDY6wARrLCEwyGKRBvuWYyvW6mHYv72SJJAH3iJN3a6eVUvkjFkcxah1bcTgGVBBrFdIopBJPhCQFMLyXw== +events@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= + events@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" @@ -12230,6 +12397,13 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= +meilisearch@^0.18.1: + version "0.18.2" + resolved "https://registry.yarnpkg.com/meilisearch/-/meilisearch-0.18.2.tgz#e26d33a4bf163b2d96b14015063ec5979a004793" + integrity sha512-L65u+yNNtIyai3V1XB7BOq+fAt6MzB2KtsqvaW6GNahPtWCZF9SKvWOl6fjhzH3KOHDyT++A65UoYJP4J7vnVA== + dependencies: + cross-fetch "^3.1.4" + memfs@^3.1.2: version "3.2.2" resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.2.2.tgz#5de461389d596e3f23d48bb7c2afb6161f4df40e" @@ -14401,7 +14575,7 @@ react-error-overlay@^6.0.9: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew== -react-fast-compare@^3.0.1, react-fast-compare@^3.2.0: +react-fast-compare@^3.0.0, react-fast-compare@^3.0.1, react-fast-compare@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== @@ -14426,6 +14600,28 @@ react-inspector@^5.1.0: is-dom "^1.0.0" prop-types "^15.0.0" +react-instantsearch-core@^6.11.2: + version "6.11.2" + resolved "https://registry.yarnpkg.com/react-instantsearch-core/-/react-instantsearch-core-6.11.2.tgz#5d70b04b02a91f2729e664156e6cd5203fae2c26" + integrity sha512-DSvS8XRESmhuBp9q+lhhsGqEKupWJioe95CCelUH0RoB8RtdC2vXRvBMDBEqTf7vG5K7b/dbbObBj5PnMqv5Sw== + dependencies: + "@babel/runtime" "^7.1.2" + algoliasearch-helper "^3.4.3" + prop-types "^15.6.2" + react-fast-compare "^3.0.0" + +react-instantsearch-dom@^6.11.2: + version "6.11.2" + resolved "https://registry.yarnpkg.com/react-instantsearch-dom/-/react-instantsearch-dom-6.11.2.tgz#048e8934dfac472eb59a16fa0125fda0669334c6" + integrity sha512-n6d0E9rreGHIo88OuWHagabAwS9/6NQ/vxRivi0n1Zfqhkaota6QTh83Ay2HIBnio1kRuJgqJhLpO+85R4vzvQ== + dependencies: + "@babel/runtime" "^7.1.2" + algoliasearch-helper "^3.4.3" + classnames "^2.2.5" + prop-types "^15.6.2" + react-fast-compare "^3.0.0" + react-instantsearch-core "^6.11.2" + react-is@17.0.2, react-is@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"