From e241680a8a30df570aa421670f1750c1082fe63a Mon Sep 17 00:00:00 2001 From: giannif Date: Mon, 31 Aug 2020 19:42:10 -0700 Subject: [PATCH 1/3] feat(search-bar): clean up / docs --- .../src/components/search-bar/context.tsx | 4 +++- .../components/search-bar/search-bar-channel.tsx | 13 ++++++++----- .../src/components/search-bar/theme.ts | 12 +++--------- .../react-components/stories/search-bar.stories.tsx | 12 +++++------- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/packages/react-components/src/components/search-bar/context.tsx b/packages/react-components/src/components/search-bar/context.tsx index 6da6e0a3..f1c3c523 100644 --- a/packages/react-components/src/components/search-bar/context.tsx +++ b/packages/react-components/src/components/search-bar/context.tsx @@ -69,7 +69,9 @@ const SearchContextManager = ({ children, options = {}, apiKey, theme, initialTe } const fetchChannelSearch = async (offset: number) => { const result = await fetch( - `https://api.giphy.com/v1/channels/search?q=${channelSearch}&offset=${offset}&api_key=${apiKey}` + `https://api.giphy.com/v1/channels/search?q=${encodeURIComponent( + channelSearch + )}&offset=${offset}&api_key=${apiKey}` ) const { data } = await result.json() return data as IChannel[] diff --git a/packages/react-components/src/components/search-bar/search-bar-channel.tsx b/packages/react-components/src/components/search-bar/search-bar-channel.tsx index ce1f011e..04b94d9d 100644 --- a/packages/react-components/src/components/search-bar/search-bar-channel.tsx +++ b/packages/react-components/src/components/search-bar/search-bar-channel.tsx @@ -9,6 +9,9 @@ import { mobileQuery, SearchTheme } from './theme' const channelMargin = 6 +const channelSearchHeight = (theme: SearchTheme) => theme.searchbarHeight - channelMargin * 2 +const smallChannelSearchHeight = (theme: SearchTheme) => theme.smallSearchbarHeight - 3 * 2 + const animateAvatar = (h: number) => keyframes` to { width: ${h}px; @@ -16,13 +19,13 @@ to { ` const Avatar = styled(Avatar_)` - height: ${(props) => (props.theme as SearchTheme).channelSearch}px; + height: ${(props) => channelSearchHeight(props.theme as SearchTheme)}px; margin: 0; width: 0; - animation: ${(props) => animateAvatar((props.theme as SearchTheme).channelSearch)} 100ms ease-in-out forwards; + animation: ${(props) => animateAvatar(channelSearchHeight(props.theme as SearchTheme))} 100ms ease-in-out forwards; @media (${mobileQuery}) { - height: ${(props) => (props.theme as SearchTheme).smallChannelSearch}px; - animation: ${(props) => animateAvatar((props.theme as SearchTheme).smallChannelSearch)} 100ms ease-in-out + height: ${(props) => smallChannelSearchHeight(props.theme as SearchTheme)}px; + animation: ${(props) => animateAvatar(smallChannelSearchHeight(props.theme as SearchTheme))} 100ms ease-in-out forwards; } ` @@ -43,7 +46,7 @@ const UsernamePill = styled.div` font-weight: 600; font-size: 12px; align-items: center; - height: ${(props) => (props.theme as SearchTheme).channelSearch}px; + height: ${(props) => channelSearchHeight(props.theme as SearchTheme)}px; @media (${mobileQuery}) { display: none; } diff --git a/packages/react-components/src/components/search-bar/theme.ts b/packages/react-components/src/components/search-bar/theme.ts index e32943da..6ad385d3 100644 --- a/packages/react-components/src/components/search-bar/theme.ts +++ b/packages/react-components/src/components/search-bar/theme.ts @@ -3,24 +3,18 @@ import { css } from '@emotion/core' export const mobileQuery = `max-width: 480px` export type SearchTheme = { + // the height of the search bar on desktop searchbarHeight: number + // the height of the search bar on mobile smallSearchbarHeight: number - channelSearch: number - smallChannelSearch: number } -const channelSearchSize = (size: number, margin = 6) => size - margin * 2 export const initTheme = (theme?: Partial): SearchTheme => { - const defaultTheme = { + return { searchbarHeight: 42, smallSearchbarHeight: 35, ...theme, } - return { - ...defaultTheme, - channelSearch: channelSearchSize(defaultTheme.searchbarHeight), - smallChannelSearch: channelSearchSize(defaultTheme.smallSearchbarHeight, 3), - } } // DRY but kinda ugly diff --git a/packages/react-components/stories/search-bar.stories.tsx b/packages/react-components/stories/search-bar.stories.tsx index 09bbd30a..fdfaf061 100644 --- a/packages/react-components/stories/search-bar.stories.tsx +++ b/packages/react-components/stories/search-bar.stories.tsx @@ -66,13 +66,11 @@ const Components = () => { ) } -export const SearchExperience = () => { - return ( - - - - ) -} +export const SearchExperience = () => ( + + + +) export const SearchExperienceInitialTerm = () => { return ( From f198ab7d6e2933e1485aea1bc96a1d934d5dbdef Mon Sep 17 00:00:00 2001 From: giannif Date: Tue, 1 Sep 2020 09:56:35 -0700 Subject: [PATCH 2/3] refactor(react-components): search bar refactor --- packages/react-components/README.md | 69 +++++++++++++++++++ .../src/components/search-bar/index.tsx | 11 +-- .../search-bar/search-bar-channel.tsx | 18 +++-- .../components/search-bar/search-button.tsx | 5 +- .../search-bar/suggestion-bar/index.tsx | 5 +- .../search-bar/suggestion-bar/pills.tsx | 5 +- .../src/components/search-bar/theme.ts | 5 ++ 7 files changed, 94 insertions(+), 24 deletions(-) diff --git a/packages/react-components/README.md b/packages/react-components/README.md index ab2950ea..5680a209 100644 --- a/packages/react-components/README.md +++ b/packages/react-components/README.md @@ -90,6 +90,9 @@ ReactDOM.render(, t ## Gif +Displays a single GIF. The building block of the [Grid](#grid) and [Carousel](#carousel). If you want to build a custom layout component, +using this will make it easy to do so. + _Gif props_ | _prop_ | _type_ | _default_ | _description_ | @@ -116,6 +119,72 @@ const { data } = await gf.gif('fpXxIjftmkk9y') ReactDOM.render(, target) ``` +### Search Experience + +The search experience encompases a search bar and a UI component. The UI component can be the [Grid](#grid) or [#Carousel](#carousel), or a a custom component +that can render an array of Gif objects. To connect the search bar to the UI component, we use `Context` + +```tsx +import { + Grid, // our UI Component to display the results + SearchBar, // the search bar the user will type into + SearchContext, // the context that wraps and connects our components + SearchContextManager, // the context manager, includes the Context.Provider + SuggestionBar, // an optional UI component that displays trending searches and channel / username results +} from '@giphy/react-components' + +// see codesanbox for runnable code +const SearchExperience = () => ( + + + +) + +// define the components in a separate function so we can +// use the context hook +const Components = () => { + const { fetchGifs, searchKey } = useContext(SearchContext) + return ( + <> + + + + + ) +} +``` + +#### SearchContextManager + +Theme is passed to the [SearchContextManager](#searchcontextmanager) + +| _prop_ | _type_ | _default_ | _description_ | +| ----------- | ---------------------------------------------------------------------------------------------------------- | ------------- | -------------------------------------------------------------------------------- | +| apiKey | string | undefined | Your api key | +| initialTerm | string | '' | _Advanced Usage_ a search term to fetch and render when the component is mounted | +| theme | [SearchTheme](#searchtheme) | default theme | A few theming options such as search bar height and dark or light mode | +| options | [SearchOptions](https://github.com/Giphy/giphy-js/blob/master/packages/fetch-api/README.md#search-options) | undefined | Search options that will be passed on to the search request | + +#### Searchbar + +An input field used in the [Search Experience](#search-experience). + +| _prop_ | _type_ | _default_ | _description_ | +| ----------- | ------------- | -------------- | ---------------------------------------------------------------------------- | +| placeholder | `string` | `Search GIPHY` | The text displayed when no text is entered. | +| theme | `SearchTheme` | default theme | Some theme properties | +| clear | `boolean` | false | Advanced use - clears the input but will leave the term in the SearchContext | + +#### Search Theme + +Theme is passed to the [SearchContextManager](#searchcontextmanager) + +| _prop_ | _type_ | _default_ | _description_ | +| -------------------- | -------------- | --------- | ----------------------------------------------- | +| mode | `dark | light` | `light` | dark or light | +| searchbarHeight | number | 42 | Height of the search bar | +| smallSearchbarHeight | number | 35 | Height of the search bar for mobile media query | + ### GifOverlay The overlay prop, available on all components allows you to overlay a gif with a custom UI and respond to hover events (desktop only). diff --git a/packages/react-components/src/components/search-bar/index.tsx b/packages/react-components/src/components/search-bar/index.tsx index 0ee22749..7cd2e192 100644 --- a/packages/react-components/src/components/search-bar/index.tsx +++ b/packages/react-components/src/components/search-bar/index.tsx @@ -1,12 +1,11 @@ import { css } from '@emotion/core' -import styled from '@emotion/styled' -import { giphyIndigo, giphyLightGrey } from '@giphy/js-brand' +import { giphyBlack, giphyCharcoal, giphyIndigo, giphyLightGrey, giphyWhite } from '@giphy/js-brand' import React, { useContext, useEffect, useRef, useState } from 'react' import useDebounce from 'react-use/lib/useDebounce' import { SearchContext } from './context' import SearchBarChannel from './search-bar-channel' import SearchButton from './search-button' -import { getSize, SearchTheme } from './theme' +import styled, { getSize } from './theme' function usePrevious(value: T) { const ref = useRef(value) @@ -106,10 +105,11 @@ const SearchBar = ({ className, placeholder = 'Search GIPHY', clear = false }: P const Container = styled.div` display: flex; background: white; - ${(props) => getSize(props.theme as SearchTheme)} + ${(props) => getSize(props.theme)} ` const Input = styled.input<{ isUsernameSearch: boolean }>` + background: ${(props) => (props.theme.mode === 'dark' ? giphyCharcoal : giphyWhite)}; box-sizing: border-box; border: 0; appearance: none; @@ -119,8 +119,9 @@ const Input = styled.input<{ isUsernameSearch: boolean }>` padding: 0 10px; border-radius: 0; text-overflow: ellipsis; + color: ${(props) => (props.theme.mode === 'dark' ? giphyWhite : giphyBlack)}; &::placeholder { - color: ${giphyLightGrey}; + color: ${(props) => (props.theme.mode === 'dark' ? giphyLightGrey : giphyLightGrey)}; } min-width: 150px; flex: 1; diff --git a/packages/react-components/src/components/search-bar/search-bar-channel.tsx b/packages/react-components/src/components/search-bar/search-bar-channel.tsx index 04b94d9d..aaa980e1 100644 --- a/packages/react-components/src/components/search-bar/search-bar-channel.tsx +++ b/packages/react-components/src/components/search-bar/search-bar-channel.tsx @@ -1,11 +1,10 @@ import { keyframes } from '@emotion/core' -import styled from '@emotion/styled' -import { giphyDarkCharcoal, giphyLightestGrey } from '@giphy/js-brand' +import { giphyCharcoal, giphyDarkCharcoal, giphyLightestGrey, giphyWhite } from '@giphy/js-brand' import React, { useContext } from 'react' import Avatar_ from '../attribution/avatar' import VerifiedBadge from '../attribution/verified-badge' import { SearchContext } from './context' -import { mobileQuery, SearchTheme } from './theme' +import styled, { mobileQuery, SearchTheme } from './theme' const channelMargin = 6 @@ -19,19 +18,18 @@ to { ` const Avatar = styled(Avatar_)` - height: ${(props) => channelSearchHeight(props.theme as SearchTheme)}px; + height: ${(props) => channelSearchHeight(props.theme)}px; margin: 0; width: 0; - animation: ${(props) => animateAvatar(channelSearchHeight(props.theme as SearchTheme))} 100ms ease-in-out forwards; + animation: ${(props) => animateAvatar(channelSearchHeight(props.theme))} 100ms ease-in-out forwards; @media (${mobileQuery}) { - height: ${(props) => smallChannelSearchHeight(props.theme as SearchTheme)}px; - animation: ${(props) => animateAvatar(smallChannelSearchHeight(props.theme as SearchTheme))} 100ms ease-in-out - forwards; + height: ${(props) => smallChannelSearchHeight(props.theme)}px; + animation: ${(props) => animateAvatar(smallChannelSearchHeight(props.theme))} 100ms ease-in-out forwards; } ` const Username = styled.div` - background: white; + background: ${(props) => (props.theme.mode === 'dark' ? giphyCharcoal : giphyWhite)}; display: flex; align-items: center; padding-left: ${channelMargin}px; @@ -46,7 +44,7 @@ const UsernamePill = styled.div` font-weight: 600; font-size: 12px; align-items: center; - height: ${(props) => channelSearchHeight(props.theme as SearchTheme)}px; + height: ${(props) => channelSearchHeight(props.theme)}px; @media (${mobileQuery}) { display: none; } diff --git a/packages/react-components/src/components/search-bar/search-button.tsx b/packages/react-components/src/components/search-bar/search-button.tsx index 86ed8634..49b6354f 100644 --- a/packages/react-components/src/components/search-bar/search-button.tsx +++ b/packages/react-components/src/components/search-bar/search-button.tsx @@ -1,10 +1,9 @@ import { keyframes } from '@emotion/core' -import styled from '@emotion/styled' import React, { useContext } from 'react' import useThrottle from 'react-use/lib/useThrottle' import { SearchContext } from './context' import SearchIcon_ from './search-icon' -import { getSize, SearchTheme } from './theme' +import styled, { getSize } from './theme' const time = '2s' const purp = '#9933FF' @@ -56,7 +55,7 @@ const Container = styled.div` @media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none) { display: none; } - ${(props) => getSize(props.theme as SearchTheme, true)} + ${(props) => getSize(props.theme, true)} ` const GradientBox = styled.div` diff --git a/packages/react-components/src/components/search-bar/suggestion-bar/index.tsx b/packages/react-components/src/components/search-bar/suggestion-bar/index.tsx index ec80dba5..0de0f61f 100644 --- a/packages/react-components/src/components/search-bar/suggestion-bar/index.tsx +++ b/packages/react-components/src/components/search-bar/suggestion-bar/index.tsx @@ -1,8 +1,7 @@ -import styled from '@emotion/styled' import { IChannel } from '@giphy/js-types' import React, { useContext, useEffect, useState } from 'react' import { SearchContext } from '../context' -import { getSize, SearchTheme } from '../theme' +import styled, { getSize } from '../theme' import { ChannelPill, TrendingSearchPill } from './pills' const Container = styled.div` @@ -16,7 +15,7 @@ const Container = styled.div` overflow-x: auto; overflow-y: hidden; padding-bottom: 10px; - ${(props) => getSize(props.theme as SearchTheme)} + ${(props) => getSize(props.theme)} ` const SuggestionBar = () => { diff --git a/packages/react-components/src/components/search-bar/suggestion-bar/pills.tsx b/packages/react-components/src/components/search-bar/suggestion-bar/pills.tsx index f6af8a3b..6c98cf18 100644 --- a/packages/react-components/src/components/search-bar/suggestion-bar/pills.tsx +++ b/packages/react-components/src/components/search-bar/suggestion-bar/pills.tsx @@ -1,11 +1,10 @@ -import styled from '@emotion/styled' import { giphyDarkestGrey } from '@giphy/js-brand' import { IChannel } from '@giphy/js-types' import React, { useContext } from 'react' import Avatar_ from '../../attribution/avatar' import VerifiedBadge from '../../attribution/verified-badge' import { SearchContext } from '../context' -import { getSize, SearchTheme } from '../theme' +import styled, { getSize } from '../theme' import TrendingIcon_ from './trending-icon' const margin = 9 @@ -31,7 +30,7 @@ const TrendingSearchPillContainer = styled.div` ` const Avatar = styled(Avatar_)` - ${(props) => getSize(props.theme as SearchTheme, true)} + ${(props) => getSize(props.theme, true)} ` const TrendingIcon = styled(TrendingIcon_)` diff --git a/packages/react-components/src/components/search-bar/theme.ts b/packages/react-components/src/components/search-bar/theme.ts index 6ad385d3..c2d2d90f 100644 --- a/packages/react-components/src/components/search-bar/theme.ts +++ b/packages/react-components/src/components/search-bar/theme.ts @@ -1,8 +1,10 @@ import { css } from '@emotion/core' +import styled, { CreateStyled } from '@emotion/styled' export const mobileQuery = `max-width: 480px` export type SearchTheme = { + mode: 'dark' | 'light' // the height of the search bar on desktop searchbarHeight: number // the height of the search bar on mobile @@ -11,6 +13,7 @@ export type SearchTheme = { export const initTheme = (theme?: Partial): SearchTheme => { return { + mode: 'light', searchbarHeight: 42, smallSearchbarHeight: 35, ...theme, @@ -32,3 +35,5 @@ export const getSize = (theme: SearchTheme, includeWidth: boolean = false) => cs `}; } ` + +export default styled as CreateStyled From 1beec48cd082a4c700a6623c28a2966dc8badd4b Mon Sep 17 00:00:00 2001 From: giannif Date: Tue, 1 Sep 2020 14:14:43 -0700 Subject: [PATCH 3/3] docs(react-components): search bar docs wip --- packages/react-components/README.md | 54 ++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/packages/react-components/README.md b/packages/react-components/README.md index 5680a209..414a2c07 100644 --- a/packages/react-components/README.md +++ b/packages/react-components/README.md @@ -121,8 +121,9 @@ ReactDOM.render(, target) ### Search Experience -The search experience encompases a search bar and a UI component. The UI component can be the [Grid](#grid) or [#Carousel](#carousel), or a a custom component -that can render an array of Gif objects. To connect the search bar to the UI component, we use `Context` +The search experience is built on a search bar and a separate visual component that can display content, which can be a [Grid](#grid) or [Carousel](#carousel), or a custom component that can fetch and render an array of `IGif` objects. To create the search experience, we use `React.Context` to set up communication between the search bar and other components by defining them as children of a [SearchContextManager](#searchcontextmanager). We recommend using the [SuggestionBar](#suggestionbar) to display trending searches and enable username searches when a user searches by username (e.g. `@nba`). + +See [codesandbox](https://codesandbox.io/s/giphyreact-components-hbmcf?from-embed) for runnable code ```tsx import { @@ -133,7 +134,7 @@ import { SuggestionBar, // an optional UI component that displays trending searches and channel / username results } from '@giphy/react-components' -// see codesanbox for runnable code +// the search experience consists of the manager and its child components that use SearchContext const SearchExperience = () => ( @@ -141,7 +142,7 @@ const SearchExperience = () => ( ) // define the components in a separate function so we can -// use the context hook +// use the context hook. You could also use the render props pattern const Components = () => { const { fetchGifs, searchKey } = useContext(SearchContext) return ( @@ -156,12 +157,14 @@ const Components = () => { #### SearchContextManager -Theme is passed to the [SearchContextManager](#searchcontextmanager) +This component manages the [SearchContext](#searchcontext) that the child components access. + +It has a few initialization props: | _prop_ | _type_ | _default_ | _description_ | | ----------- | ---------------------------------------------------------------------------------------------------------- | ------------- | -------------------------------------------------------------------------------- | | apiKey | string | undefined | Your api key | -| initialTerm | string | '' | _Advanced Usage_ a search term to fetch and render when the component is mounted | +| initialTerm | string | '' | _Advanced usage_ a search term to fetch and render when the component is mounted | | theme | [SearchTheme](#searchtheme) | default theme | A few theming options such as search bar height and dark or light mode | | options | [SearchOptions](https://github.com/Giphy/giphy-js/blob/master/packages/fetch-api/README.md#search-options) | undefined | Search options that will be passed on to the search request | @@ -169,21 +172,38 @@ Theme is passed to the [SearchContextManager](#searchcontextmanager) An input field used in the [Search Experience](#search-experience). -| _prop_ | _type_ | _default_ | _description_ | -| ----------- | ------------- | -------------- | ---------------------------------------------------------------------------- | -| placeholder | `string` | `Search GIPHY` | The text displayed when no text is entered. | -| theme | `SearchTheme` | default theme | Some theme properties | -| clear | `boolean` | false | Advanced use - clears the input but will leave the term in the SearchContext | +| _prop_ | _type_ | _default_ | _description_ | +| ----------- | ------------- | -------------- | --------------------------------------------------------------------------------- | +| placeholder | `string` | `Search GIPHY` | The text displayed when no text is entered | +| theme | `SearchTheme` | default theme | See (SearchTheme)[#searchtheme] | +| clear | `boolean` | false | _Advanced useage_ - clears the input but will leave the term in the SearchContext | + +#### SearchContext -#### Search Theme +The `SearchContext` manages the state of the search experience. The props below are all you need to configure your UI component. See (Search Experience)[#search-experience]. +It should use `searchKey` as its key, so when we have a new search, the old content is removed. And it will need a `fetchGifs` to initiate the first fetch and for subsequent infinite scroll fetches + +| _prop_ | _type_ | _default_ | _description_ | +| --------- | ----------------------------------------- | -------------------- | ----------------------------------------------------------------------------------- | +| searchKey | string | undefined | A unique id of the current search, used as a React key to refresh the [Grid](#grid) | +| fetchGifs | `(offset: number) => Promise` | default search fetch | The search request passed to the UI component | + +#### SearchTheme Theme is passed to the [SearchContextManager](#searchcontextmanager) -| _prop_ | _type_ | _default_ | _description_ | -| -------------------- | -------------- | --------- | ----------------------------------------------- | -| mode | `dark | light` | `light` | dark or light | -| searchbarHeight | number | 42 | Height of the search bar | -| smallSearchbarHeight | number | 35 | Height of the search bar for mobile media query | +| _prop_ | _type_ | _default_ | _description_ | +| -------------------- | ----------------- | --------- | --------------------------------------------------------- | +| mode | `dark` \| `light` | `light` | dark or light | +| searchbarHeight | number | 42 | Height of the search bar | +| smallSearchbarHeight | number | 35 | Height of the search bar when matching mobile media query | + +#### SuggestionBar + +Display scrolling trending searches and username search. When clicking a trending search term, the search input will be +populated with that term and the search will be fetched and rendered. + +If a user types a username into the search bar such as `@nba`, a username search will done and the all the channels that match will be displayed in the suggestion bar. When clicking a username, the search bar will go into username search mode. ### GifOverlay