diff --git a/src/components/SectionSearchLinks/SectionSearchLinks.css b/src/components/SectionSearchLinks/SectionSearchLinks.css new file mode 100644 index 00000000..53508686 --- /dev/null +++ b/src/components/SectionSearchLinks/SectionSearchLinks.css @@ -0,0 +1,115 @@ +@import '../../marketplace.css'; + +.root { +} + +.heading { + @apply --marketplaceH1FontStyles; + margin: 0 0 18px 0; + + @media (--viewportMedium) { + margin: 0 0 23px 0; + } +} + +.subHeading { + margin: 0 0 57px 0; + + @media (--viewportMedium) { + margin: 0 0 57px 0; + } +} + +.links { + display: flex; + flex-direction: column; + justify-content: space-between; + flex-wrap: wrap; + + @media (--viewportMedium) { + flex-direction: row; + } +} + +.link { + width: 100%; + margin-top: 25px; + + &:hover { + text-decoration: none; + } + + /* First link should not have top margin */ + &:nth-of-type(1) { + margin-top: 0; + } +} + +.link2Columns { + @media (--viewportMedium) { + width: calc(50% - 20px); + + /* First row should not have top margin */ + &:nth-of-type(1), + &:nth-of-type(2) { + margin-top: 0; + } + } +} + +.link3Columns { + @media (--viewportMedium) { + width: calc(33.333% - 20px); + + /* First row should not have top margin */ + &:nth-of-type(1), + &:nth-of-type(2), + &:nth-of-type(3) { + margin-top: 0; + } + } +} + +.imageWrapper { + position: relative; + width: 100%; + border-radius: 4px; + transition: var(--transitionStyleButton); + + &:hover { + transform: scale(1.02); + box-shadow: var(--boxShadowSectionLocationHover); + } +} + +.aspectWrapper { + padding-bottom: calc(6 / 13 * 100%); /* 13:6 Aspect Ratio */ + + @media (--viewportMedium) { + padding-bottom: calc(2 / 3 * 100%); /* 3:2 Aspect Ratio */ + } +} + +.image { + /* Layout - image will take space defined by aspect ratio wrapper */ + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 100%; + height: 100%; + border-radius: 4px; + object-fit: cover; +} + +.text { + @apply --marketplaceH2FontStyles; + color: var(--matterColor); + margin-top: 15px; + margin-bottom: 0; + + @media (--viewportMedium) { + margin-top: 21px; + } +} diff --git a/src/components/SectionSearchLinks/SectionSearchLinks.example.js b/src/components/SectionSearchLinks/SectionSearchLinks.example.js new file mode 100644 index 00000000..85fc5a36 --- /dev/null +++ b/src/components/SectionSearchLinks/SectionSearchLinks.example.js @@ -0,0 +1,137 @@ +import SectionSearchLinks from './SectionSearchLinks'; + +const imageAltText = 'styleguide alt text'; + +export const TwoLinksWithHeadings = { + component: SectionSearchLinks, + props: { + linksPerRow: 2, + links: [ + { + imageUrl: 'http://lorempixel.com/648/448/', + imageAltText, + searchQuery: '?1', + text: 'Link 1', + }, + { + imageUrl: 'http://lorempixel.com/648/448/', + imageAltText, + searchQuery: '?2', + text: 'Link 2', + }, + ], + heading: 'Two links side by side', + subHeading: 'One column in mobile, two columns in desktop.', + }, + group: 'sections', +}; + +export const ThreeLinksWithHeadings = { + component: SectionSearchLinks, + props: { + linksPerRow: 3, + links: [ + { + imageUrl: 'http://lorempixel.com/648/448/', + imageAltText, + searchQuery: '?1', + text: 'Link 1', + }, + { + imageUrl: 'http://lorempixel.com/648/448/', + imageAltText, + searchQuery: '?2', + text: 'Link 2', + }, + { + imageUrl: 'http://lorempixel.com/648/448/', + imageAltText, + searchQuery: '?3', + text: 'Link 3', + }, + ], + heading: 'Three links side by side', + subHeading: 'One column in mobile, three columns in desktop.', + }, + group: 'sections', +}; + +export const FourLinks = { + component: SectionSearchLinks, + props: { + linksPerRow: 2, + links: [ + { + imageUrl: 'http://lorempixel.com/648/448/', + imageAltText, + searchQuery: '?1', + text: 'Link 1 with quite a long text that tests how the items below align', + }, + { + imageUrl: 'http://lorempixel.com/648/448/', + imageAltText, + searchQuery: '?2', + text: 'Link 2', + }, + { + imageUrl: 'http://lorempixel.com/648/448/', + imageAltText, + searchQuery: '?3', + text: 'Link 3', + }, + { + imageUrl: 'http://lorempixel.com/648/448/', + imageAltText, + searchQuery: '?4', + text: 'Link 4', + }, + ], + }, + group: 'sections', +}; + +export const SixLinks = { + component: SectionSearchLinks, + props: { + linksPerRow: 3, + links: [ + { + imageUrl: 'http://lorempixel.com/648/448/', + imageAltText, + searchQuery: '?1', + text: 'Link 1', + }, + { + imageUrl: 'http://lorempixel.com/648/448/', + imageAltText, + searchQuery: '?2', + text: 'Link 2', + }, + { + imageUrl: 'http://lorempixel.com/648/448/', + imageAltText, + searchQuery: '?3', + text: 'Link 3', + }, + { + imageUrl: 'http://lorempixel.com/648/448/', + imageAltText, + searchQuery: '?4', + text: 'Link 4', + }, + { + imageUrl: 'http://lorempixel.com/648/448/', + imageAltText, + searchQuery: '?5', + text: 'Link 5', + }, + { + imageUrl: 'http://lorempixel.com/648/448/', + imageAltText, + searchQuery: '?6', + text: 'Link 6', + }, + ], + }, + group: 'sections', +}; diff --git a/src/components/SectionSearchLinks/SectionSearchLinks.js b/src/components/SectionSearchLinks/SectionSearchLinks.js new file mode 100644 index 00000000..538e11b1 --- /dev/null +++ b/src/components/SectionSearchLinks/SectionSearchLinks.js @@ -0,0 +1,86 @@ +import React from 'react'; +import { string, arrayOf, shape, node, oneOf } from 'prop-types'; +import classNames from 'classnames'; +import { NamedLink } from '../../components'; + +import css from './SectionSearchLinks.css'; + +const SearchLink = props => { + const { linksPerRow, imageUrl, imageAltText, searchQuery, text } = props; + const classes = classNames(css.link, { + [css.link2Columns]: linksPerRow === 2, + [css.link3Columns]: linksPerRow === 3, + }); + return ( + +
+
+ {imageAltText} +
+
+
{text}
+
+ ); +}; + +const SectionSearchLinks = props => { + const { + rootClassName, + className, + linksPerRow, + links, + heading, + subHeading, + headingRootClassName, + subHeadingRootClassName, + } = props; + const classes = classNames(rootClassName || css.root, className); + const headingClasses = headingRootClassName || css.heading; + const subHeadingClasses = subHeadingRootClassName || css.subHeading; + return ( +
+ {heading ?

{heading}

: null} + {subHeading ?

{subHeading}

: null} +
+ {links.map(link => ( + + ))} +
+
+ ); +}; + +SectionSearchLinks.defaultProps = { + rootClassName: null, + className: null, + heading: null, + subHeading: null, + headingRootClassName: null, + subHeadingRootClassName: null, +}; + +SectionSearchLinks.propTypes = { + rootClassName: string, + className: string, + + linksPerRow: oneOf([2, 3]).isRequired, + links: arrayOf( + shape({ + imageUrl: string.isRequired, + imageAltText: string.isRequired, + searchQuery: string.isRequired, + text: node.isRequired, + }) + ).isRequired, + + // Styles are defined with the assumption that either both the + // heading and the subHeading are given, or neither of them are. If + // only one of them is given, the margins most likely won't make + // sense. + heading: node, + subHeading: node, + headingRootClassName: string, + subHeadingRootClassName: string, +}; + +export default SectionSearchLinks; diff --git a/src/components/index.js b/src/components/index.js index 40164a57..d0d271bc 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -98,6 +98,7 @@ export { default as SearchResultsPanel } from './SearchResultsPanel/SearchResult export { default as SectionHero } from './SectionHero/SectionHero'; export { default as SectionHowItWorks } from './SectionHowItWorks/SectionHowItWorks'; export { default as SectionLocations } from './SectionLocations/SectionLocations'; +export { default as SectionSearchLinks } from './SectionSearchLinks/SectionSearchLinks'; export { default as SelectField } from './SelectField/SelectField'; export { default as SelectSingleFilter } from './SelectSingleFilter/SelectSingleFilter'; export { diff --git a/src/examples.js b/src/examples.js index 28408330..9a35f297 100644 --- a/src/examples.js +++ b/src/examples.js @@ -40,6 +40,7 @@ import * as PaginationLinks from './components/PaginationLinks/PaginationLinks.e import * as ResponsiveImage from './components/ResponsiveImage/ResponsiveImage.example'; import * as ReviewRating from './components/ReviewRating/ReviewRating.example'; import * as Reviews from './components/Reviews/Reviews.example'; +import * as SectionSearchLinks from './components/SectionSearchLinks/SectionSearchLinks.example'; import * as SelectField from './components/SelectField/SelectField.example'; import * as StripeBankAccountTokenInputField from './components/StripeBankAccountTokenInputField/StripeBankAccountTokenInputField.example'; import * as TabNav from './components/TabNav/TabNav.example'; @@ -123,6 +124,7 @@ export { ReviewForm, ReviewRating, Reviews, + SectionSearchLinks, SelectField, SendMessageForm, SignupForm,