digitalfabrik/integreat-app

View on GitHub
native/src/components/SearchListItem.tsx

Summary

Maintainability
A
0 mins
Test Coverage
A
97%
import React, { memo, ReactElement } from 'react'
import { useTranslation } from 'react-i18next'
import { View } from 'react-native'
import Highlighter from 'react-native-highlight-words'
import styled, { useTheme } from 'styled-components/native'

import { getExcerpt, InternalPathnameParser, normalizeString, SEARCH_FINISHED_SIGNAL_NAME } from 'shared'

import { SEARCH_PREVIEW_MAX_CHARS } from '../constants'
import buildConfig from '../constants/buildConfig'
import { contentDirection } from '../constants/contentDirection'
import useNavigate from '../hooks/useNavigate'
import urlFromRouteInformation from '../navigation/url'
import { PageResourceCacheStateType } from '../utils/DataContainer'
import sendTrackingSignal from '../utils/sendTrackingSignal'
import { CategoryThumbnail } from './CategoryListItem'
import Pressable from './base/Pressable'

const FlexStyledLink = styled(Pressable)`
  display: flex;
  flex-direction: column;
  margin: 0 20px;
`

const DirectionContainer = styled.View<{ language: string }>`
  display: flex;
  flex-direction: ${props => contentDirection(props.language)};
`

const SearchEntryContainer = styled.View`
  flex: 1;
  flex-direction: column;
  align-self: center;
  padding: 15px 0;
  border-bottom-width: 1px;
  border-bottom-color: ${props => props.theme.colors.themeColor};
`

const TitleDirectionContainer = styled.View<{ language: string }>`
  flex-direction: ${props => contentDirection(props.language)};
  align-items: center;
`

const HighlighterCategoryTitle = styled(Highlighter)<{ language: string }>`
  flex-direction: ${props => contentDirection(props.language)};
  font-family: ${props => props.theme.fonts.native.decorativeFontRegular};
  color: ${props => props.theme.colors.textColor};
  font-weight: bold;
  flex-shrink: 1;
`

type SearchListItemProps = {
  title: string
  contentWithoutHtml: string
  resourceCache: PageResourceCacheStateType
  language: string
  query: string
  path: string
  thumbnail: string | null
}

const SearchListItem = ({
  language,
  title,
  resourceCache,
  contentWithoutHtml,
  query,
  path,
  thumbnail,
}: SearchListItemProps): ReactElement => {
  const { t } = useTranslation('search')
  const theme = useTheme()
  const { navigateTo } = useNavigate()
  const excerpt = getExcerpt(contentWithoutHtml, { query, maxChars: SEARCH_PREVIEW_MAX_CHARS })

  const routeInformation = new InternalPathnameParser(path, language, buildConfig().featureFlags.fixedCity).route()
  if (!routeInformation) {
    return <View />
  }

  const navigateToSearchResult = (): void => {
    sendTrackingSignal({
      signal: {
        name: SEARCH_FINISHED_SIGNAL_NAME,
        query,
        url: urlFromRouteInformation(routeInformation),
      },
    })
    navigateTo(routeInformation)
  }

  const Content =
    query && excerpt.length > 0 ? (
      <Highlighter
        searchWords={[query]}
        sanitize={normalizeString}
        textToHighlight={excerpt}
        autoEscape
        highlightStyle={{ backgroundColor: theme.colors.backgroundColor, fontWeight: 'bold' }}
      />
    ) : null

  const Title = (
    <HighlighterCategoryTitle
      language={language}
      autoEscape
      textToHighlight={title}
      sanitize={normalizeString}
      searchWords={query ? [query] : []}
      highlightStyle={{
        fontWeight: 'bold',
      }}
    />
  )

  return (
    <FlexStyledLink onPress={navigateToSearchResult} role='link' accessibilityHint={t('itemHint')}>
      <DirectionContainer language={language}>
        <SearchEntryContainer>
          <TitleDirectionContainer language={language}>
            {!!thumbnail && <CategoryThumbnail language={language} source={thumbnail} resourceCache={resourceCache} />}
            {Title}
          </TitleDirectionContainer>
          {Content}
        </SearchEntryContainer>
      </DirectionContainer>
    </FlexStyledLink>
  )
}

export default memo(SearchListItem)