UNPKG

ndla-ui

Version:

UI component library for NDLA.

263 lines (250 loc) 8.32 kB
import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import BEMHelper from 'react-bem-helper'; import { ChevronRight, Additional } from 'ndla-icons/common'; import { Cross } from 'ndla-icons/action'; import { uuid } from 'ndla-util'; import { Trans } from 'ndla-i18n'; import Button from 'ndla-button'; import { FilterTabs } from 'ndla-tabs'; import Tooltip from '../Tooltip'; import SafeLink from '../common/SafeLink'; const resultClasses = BEMHelper('c-search-result'); export const SearchResult = ({ tabOptions, children, messages, searchString, currentTab, onTabChange, author, currentCompetenceGoal, competenceGoalsOpen, onToggleCompetenceGoals, competenceGoals, }) => ( <Trans> {({ t }) => ( <div {...resultClasses()}> {author || ( <div {...resultClasses('heading-wrapper')}> <h1 {...resultClasses( 'heading', currentCompetenceGoal ? 'competence-goal' : null, )}> {messages.searchStringLabel} <span>{searchString}</span> </h1> {competenceGoalsOpen && ( <Button link {...resultClasses('close-competencegoals-btn')} onClick={onToggleCompetenceGoals}> {t('competenceGoals.closeCompetenceGoals')} <Cross className="c-icon--22 u-margin-left-tiny" /> </Button> )} </div> )} <h2>{messages.subHeading}</h2> {!competenceGoalsOpen && currentCompetenceGoal && ( <ul {...resultClasses('current-goal')}> <li>{currentCompetenceGoal}</li> </ul> )} {!competenceGoalsOpen && competenceGoals !== null && ( <p {...resultClasses('current-goal-info')}> {messages.openCompetenceGoalsButtonPrefix}{' '} <Button link onClick={onToggleCompetenceGoals}> {messages.openCompetenceGoalsButton} </Button> </p> )} {competenceGoalsOpen && ( <div {...resultClasses('competence-goals')}>{competenceGoals}</div> )} {!competenceGoalsOpen && ( <Fragment> <FilterTabs dropdownBtnLabel={t( 'searchPage.searchPageMessages.dropdownBtnLabel', )} value={currentTab} options={tabOptions} contentId="search-result-content" onChange={onTabChange}> {children} </FilterTabs> <div {...resultClasses('narrow-result')}>{children}</div> </Fragment> )} </div> )} </Trans> ); SearchResult.propTypes = { tabOptions: PropTypes.arrayOf( PropTypes.shape({ title: PropTypes.string.isRequired, value: PropTypes.string.isRequired, }), ).isRequired, currentTab: PropTypes.string, children: PropTypes.node.isRequired, messages: PropTypes.shape({ searchStringLabel: PropTypes.string.isRequired, subHeading: PropTypes.string.isRequired, openCompetenceGoalsButtonPrefix: PropTypes.string, openCompetenceGoalsButton: PropTypes.string, }).isRequired, currentCompetenceGoal: PropTypes.string, competenceGoalsOpen: PropTypes.bool, onToggleCompetenceGoals: PropTypes.func, competenceGoals: PropTypes.node, searchString: (props, propName, componentName) => { if (props.author === null && typeof props[propName] !== 'string') { return new Error( `Invalid prop 'searchString' in ${componentName}. Required unless props.author === PropTypes.node`, ); } return null; }, onTabChange: PropTypes.func.isRequired, author: PropTypes.node, }; SearchResult.defaultProps = { author: null, }; const searchResultItemClasses = BEMHelper('c-search-result-item'); const searchResultItemShape = PropTypes.shape({ id: PropTypes.number.isRequired, title: PropTypes.string.isRequired, url: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, breadcrumb: PropTypes.arrayOf(PropTypes.string), subjects: PropTypes.arrayOf( PropTypes.shape({ title: PropTypes.string.isRequired, url: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, }), ), additional: PropTypes.bool, image: PropTypes.node, ingress: PropTypes.string.isRequired, contentTypeIcon: PropTypes.node.isRequired, contentTypeLabel: PropTypes.string.isRequired, }); const SearchResultItem = ({ item, subjectsLabel, additionalContentToolip }) => ( <li key={item.id} {...searchResultItemClasses()}> <article> <header {...searchResultItemClasses('header')}> <h1> {item.url.href ? ( <a {...item.url}>{item.title}</a> ) : ( <SafeLink to={item.url}>{item.title}</SafeLink> )} </h1> <div {...searchResultItemClasses('content-type-wrapper')}> {item.contentTypeIcon} <span {...searchResultItemClasses('content-type-label')}> {item.contentTypeLabel} </span> </div> {item.type && ( <div {...searchResultItemClasses('pills')}>{item.type}</div> )} {item.additional && (additionalContentToolip ? ( <Tooltip id={`search-additional-tooltip-${item.id}`} tooltip={additionalContentToolip} {...searchResultItemClasses('additional')}> <Additional className="c-icon--20" /> </Tooltip> ) : ( <span {...searchResultItemClasses('additional')}> <Additional className="c-icon--20" /> </span> ))} </header> {item.breadcrumb && item.breadcrumb.length > 0 && ( <div {...searchResultItemClasses('breadcrumb')}> {item.breadcrumb.map((breadcrumbItem, index) => { let icon = null; if (index !== item.breadcrumb.length - 1) { icon = <ChevronRight />; } return ( <Fragment key={uuid()}> <span>{breadcrumbItem}</span> {icon} </Fragment> ); })} </div> )} <div {...searchResultItemClasses('content')}> <p {...searchResultItemClasses('ingress')} dangerouslySetInnerHTML={{ __html: item.ingress }} /> {item.image} </div> {item.subjects && item.subjects.length !== 0 && ( <div {...searchResultItemClasses('subjects')}> <span>{subjectsLabel}</span> <ul> {item.subjects.map(subject => ( <li key={uuid()}> {subject.url.href ? ( <a {...subject.url}>{subject.title}</a> ) : ( <SafeLink to={subject.url}>{subject.title}</SafeLink> )} </li> ))} </ul> </div> )} </article> </li> ); SearchResultItem.propTypes = { item: searchResultItemShape.isRequired, additionalContentToolip: PropTypes.string.isRequired, subjectsLabel: PropTypes.string.isRequired, }; export const SearchResultList = ({ results }) => ( <Trans> {({ t }) => results.length === 0 ? ( <article className="c-search-result-list__empty"> <h1>{t('searchPage.searchResultListMessages.noResultHeading')}</h1> <p>{t('searchPage.searchResultListMessages.noResultDescription')}</p> </article> ) : ( <ul className="c-search-result-list"> {results.map(item => ( <SearchResultItem key={`search_result_item_${ typeof item.url === 'object' ? item.url.href : item.url }`} item={item} additionalContentToolip={t('resource.tooltipAdditionalTopic')} subjectsLabel={t( 'searchPage.searchResultListMessages.subjectsLabel', )} /> ))} </ul> ) } </Trans> ); SearchResultList.propTypes = { results: PropTypes.arrayOf(searchResultItemShape), };