-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sale header banner): L3-2394 create sale header banner (#360)
- Loading branch information
Showing
10 changed files
with
455 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
src/patterns/SaleHeaderBanner/SaleHeaderBanner.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { Meta } from '@storybook/react'; | ||
import SaleHeaderBanner, { SaleHeaderBannerProps } from './SaleHeaderBanner'; | ||
import { AuctionState } from './types'; | ||
import SaleHeaderCountdown from './SaleHeaderCountdown'; | ||
import SaleHeaderBrowseAuctions from './SaleHeaderBrowseAuctions'; | ||
|
||
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction | ||
const meta = { | ||
title: 'Patterns/SaleHeaderBanner', | ||
component: SaleHeaderBanner, | ||
} satisfies Meta<typeof SaleHeaderBanner>; | ||
|
||
export default meta; | ||
export const Playground = (props: SaleHeaderBannerProps) => <SaleHeaderBanner {...props} />; | ||
|
||
Playground.args = { | ||
auctionTitle: 'Modern & Contemporary Art: Online Auction, New York', | ||
occurrenceInformation: [{ occurrenceLabel: 'Begins', date: '10:00am EDT, 14 Sep 2024' }], | ||
location: 'New York', | ||
auctionState: AuctionState.preSale, | ||
imageSrcUrl: | ||
'https://assets.phillips.com/image/upload/t_Website_AuctionPageHero/v1726172550/auctions/NY090324/NY090324.jpg', | ||
}; | ||
|
||
Playground.argTypes = { | ||
auctionState: { | ||
options: Object.values(AuctionState), | ||
control: { | ||
type: 'select', | ||
}, | ||
}, | ||
}; | ||
|
||
export const PreSale = (props: SaleHeaderBannerProps) => ( | ||
<SaleHeaderBanner | ||
{...props} | ||
auctionTitle="Modern & Contemporary Art: Online Auction, New York" | ||
imageSrcUrl="https://assets.phillips.com/image/upload/t_Website_AuctionPageHero/v1726172550/auctions/NY090324/NY090324.jpg" | ||
occurrenceInformation={[{ date: '10:00am EDT, 4 Sep 2024', occurrenceLabel: 'Begins' }]} | ||
location="New York" | ||
auctionState={AuctionState.preSale} | ||
/> | ||
); | ||
|
||
export const PreSaleTwoOccurrences = (props: SaleHeaderBannerProps) => ( | ||
<SaleHeaderBanner | ||
{...props} | ||
auctionTitle="Modern & Contemporary Art: Online Auction, New York" | ||
imageSrcUrl="https://assets.phillips.com/image/upload/t_Website_AuctionPageHero/v1726172550/auctions/NY090324/NY090324.jpg" | ||
occurrenceInformation={[ | ||
{ date: '10:00am EDT, 4 Sep 2024', occurrenceLabel: 'Session I' }, | ||
{ date: '10:00am EDT, 5 Sep 2024', occurrenceLabel: 'Session II' }, | ||
]} | ||
location="New York" | ||
auctionState={AuctionState.preSale} | ||
/> | ||
); | ||
|
||
export const PreSaleThreeOccurrences = (props: SaleHeaderBannerProps) => ( | ||
<SaleHeaderBanner | ||
{...props} | ||
auctionTitle="Modern & Contemporary Art: Online Auction, New York" | ||
imageSrcUrl="https://assets.phillips.com/image/upload/t_Website_AuctionPageHero/v1726172550/auctions/NY090324/NY090324.jpg" | ||
occurrenceInformation={[ | ||
{ date: '10:00am EDT, 4 Sep 2024', occurrenceLabel: 'Session I' }, | ||
{ date: '10:00am EDT, 5 Sep 2024', occurrenceLabel: 'Session II' }, | ||
{ date: '10:00am EDT, 6 Sep 2024', occurrenceLabel: 'Session III' }, | ||
]} | ||
location="New York" | ||
auctionState={AuctionState.preSale} | ||
/> | ||
); | ||
|
||
export const OpenForBidding = (props: SaleHeaderBannerProps) => ( | ||
<SaleHeaderBanner | ||
{...props} | ||
auctionTitle="Modern & Contemporary Art: Online Auction, New York" | ||
imageSrcUrl="https://assets.phillips.com/image/upload/t_Website_AuctionPageHero/v1726172550/auctions/NY090324/NY090324.jpg" | ||
occurrenceInformation={[{ date: '10:00am EDT, 4 Sep 2024', occurrenceLabel: 'Lots Begin to Close' }]} | ||
location="New York" | ||
auctionState={AuctionState.openForBidding} | ||
> | ||
<SaleHeaderCountdown /> | ||
</SaleHeaderBanner> | ||
); | ||
|
||
export const Closed = (props: SaleHeaderBannerProps) => ( | ||
<SaleHeaderBanner | ||
{...props} | ||
auctionTitle="Modern & Contemporary Art: Online Auction, New York" | ||
imageSrcUrl="https://assets.phillips.com/image/upload/t_Website_AuctionPageHero/v1726172550/auctions/NY090324/NY090324.jpg" | ||
occurrenceInformation={[{ date: '4 Sep 2024', occurrenceLabel: 'Concluded' }]} | ||
location="New York" | ||
auctionState={AuctionState.past} | ||
> | ||
<SaleHeaderBrowseAuctions /> | ||
</SaleHeaderBanner> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { render, screen } from '@testing-library/react'; | ||
import { describe, it, expect } from 'vitest'; | ||
import SaleHeaderBanner, { SaleHeaderBannerProps } from './SaleHeaderBanner'; | ||
import { AuctionState } from './types'; | ||
import SaleHeaderCountdown from './SaleHeaderCountdown'; | ||
import SaleHeaderBrowseAuctions from './SaleHeaderBrowseAuctions'; | ||
|
||
const defaultProps: SaleHeaderBannerProps = { | ||
auctionTitle: 'Sample Auction', | ||
location: 'New York', | ||
occurrenceInformation: [{ date: '2023-12-01', occurrenceLabel: 'Auction Date' }], | ||
auctionState: AuctionState.preSale, | ||
imageSrcUrl: 'https://example.com/image.jpg', | ||
}; | ||
|
||
describe('SaleHeaderBanner', () => { | ||
it('renders the auction title', () => { | ||
render(<SaleHeaderBanner {...defaultProps} />); | ||
expect(screen.getByText('Sample Auction')).toBeInTheDocument(); | ||
}); | ||
|
||
it('renders the location', () => { | ||
render(<SaleHeaderBanner {...defaultProps} />); | ||
expect(screen.getByText('New York')).toBeInTheDocument(); | ||
}); | ||
|
||
it('renders the date and occurrence label', () => { | ||
render(<SaleHeaderBanner {...defaultProps} />); | ||
expect(screen.getByText('Auction Date')).toBeInTheDocument(); | ||
expect(screen.getByText('2023-12-01')).toBeInTheDocument(); | ||
}); | ||
|
||
it('renders the image with the correct src and alt attributes', () => { | ||
render(<SaleHeaderBanner {...defaultProps} />); | ||
const img = screen.getByAltText('Sample Auction'); | ||
expect(img).toHaveAttribute('src', 'https://example.com/image.jpg'); | ||
}); | ||
|
||
it('renders the "Register to Bid" button when auction is not closed', () => { | ||
render(<SaleHeaderBanner {...defaultProps} />); | ||
expect(screen.getByText('Register to Bid')).toBeInTheDocument(); | ||
}); | ||
|
||
it('does not render the "Register to Bid" button when auction is closed', () => { | ||
render(<SaleHeaderBanner {...defaultProps} auctionState={AuctionState.past} />); | ||
expect(screen.queryByText('Register to Bid')).not.toBeInTheDocument(); | ||
}); | ||
|
||
it('renders the countdown timer when auction is open for bidding', () => { | ||
render( | ||
<SaleHeaderBanner {...defaultProps} auctionState={AuctionState.openForBidding}> | ||
<SaleHeaderCountdown /> | ||
</SaleHeaderBanner>, | ||
); | ||
expect(screen.getByText('Lots Close in')).toBeInTheDocument(); | ||
}); | ||
|
||
it('renders the "Browse Upcoming Sale" link when auction is closed', () => { | ||
render( | ||
<SaleHeaderBanner {...defaultProps} auctionState={AuctionState.past}> | ||
<SaleHeaderBrowseAuctions /> | ||
</SaleHeaderBanner>, | ||
); | ||
expect(screen.getByText('Browse Upcoming Sale')).toBeInTheDocument(); | ||
expect(screen.getByText('View Calendar')).toBeInTheDocument(); | ||
}); | ||
|
||
it('renders custom CTA label when provided', () => { | ||
render(<SaleHeaderBanner {...defaultProps} ctaLabel="Join Now" />); | ||
expect(screen.getByText('Join Now')).toBeInTheDocument(); | ||
}); | ||
|
||
it('calls onClick handler when CTA button is clicked', () => { | ||
const handleClick = vi.fn(); | ||
render(<SaleHeaderBanner {...defaultProps} onClick={handleClick} />); | ||
screen.getByText('Register to Bid').click(); | ||
expect(handleClick).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('renders children when auction is open for bidding', () => { | ||
render( | ||
<SaleHeaderBanner {...defaultProps} auctionState={AuctionState.openForBidding}> | ||
<div>Child Component</div> | ||
</SaleHeaderBanner>, | ||
); | ||
expect(screen.getByText('Child Component')).toBeInTheDocument(); | ||
}); | ||
|
||
it('renders children when auction is closed', () => { | ||
render( | ||
<SaleHeaderBanner {...defaultProps} auctionState={AuctionState.past}> | ||
<div>Child Component</div> | ||
</SaleHeaderBanner>, | ||
); | ||
expect(screen.getByText('Child Component')).toBeInTheDocument(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import { ComponentProps, forwardRef } from 'react'; | ||
import { getCommonProps } from '../../utils'; | ||
import classnames from 'classnames'; | ||
import { SeldonImage } from '../../components/SeldonImage'; | ||
import { AuctionState } from './types'; | ||
import { Text, TextVariants } from '../../components/Text'; | ||
import { PageContentWrapper as PageMargin } from '../../components/PageContentWrapper'; | ||
import Button from '../../components/Button/Button'; | ||
|
||
// You'll need to change the ComponentProps<"htmlelementname"> to match the top-level element of your component | ||
export interface SaleHeaderBannerProps extends ComponentProps<'div'> { | ||
/** | ||
* What is the title of the auction? | ||
*/ | ||
auctionTitle: React.ReactNode; | ||
/** | ||
* The URL of the banner image | ||
*/ | ||
imageSrcUrl: string; | ||
/** | ||
* Where is the auction taking place? | ||
*/ | ||
location: React.ReactNode; | ||
|
||
occurrenceInformation: { | ||
/** | ||
* Depending on auction state, when does the auction open or close | ||
*/ | ||
date: React.ReactNode; | ||
/** | ||
* Clarifies the date based on the auction state | ||
*/ | ||
occurrenceLabel: React.ReactNode; | ||
}[]; | ||
|
||
/** | ||
* What is the current state of the auction? | ||
*/ | ||
auctionState: AuctionState; | ||
/** | ||
* What text should the CTA button display? | ||
*/ | ||
ctaLabel?: React.ReactNode; | ||
/** | ||
* What action does the CTA take? | ||
*/ | ||
onClick?: () => void; | ||
} | ||
/** | ||
* ## Overview | ||
* | ||
* Sale header banner component, supports 3 states of the auction: pre-sale, open for bidding, and closed. | ||
* | ||
* [Figma Link](https://www.figma.com/design/OvBXAq48blO1r4qYbeBPjW/RW---Sale-Page-(PLP)?node-id=1-6&m=dev) | ||
* | ||
* [Storybook Link](https://phillips-seldon.netlify.app/?path=/docs/patterns-saleheaderbanner--overview) | ||
*/ | ||
|
||
const SaleHeaderBanner = forwardRef<HTMLDivElement, SaleHeaderBannerProps>( | ||
( | ||
{ | ||
auctionTitle, | ||
imageSrcUrl, | ||
location, | ||
auctionState, | ||
occurrenceInformation, | ||
ctaLabel = 'Register to Bid', | ||
onClick, | ||
children, | ||
className, | ||
...props | ||
}, | ||
ref, | ||
) => { | ||
const { className: baseClassName, ...commonProps } = getCommonProps(props, 'SaleHeaderBanner'); | ||
const isOpenForBidding = auctionState === AuctionState.openForBidding; | ||
const isClosed = auctionState === AuctionState.past; | ||
return ( | ||
<div {...commonProps} className={classnames(baseClassName, className)} {...props} ref={ref}> | ||
<SeldonImage | ||
aspectRatio="16/9" | ||
src={imageSrcUrl} | ||
alt={String(auctionTitle)} | ||
objectFit="cover" | ||
className={`${baseClassName}__image`} | ||
/> | ||
<PageMargin className={`${baseClassName}__stack-wrapper`} {...commonProps} {...props} ref={ref}> | ||
<div className={`${baseClassName}__stack`}> | ||
{isOpenForBidding && children} | ||
<Text variant={TextVariants.title1}>{auctionTitle}</Text> | ||
<Text variant={TextVariants.string2} className={`${baseClassName}__location`}> | ||
{location} | ||
</Text> | ||
<div className={`${baseClassName}__occurrence-details`}> | ||
{occurrenceInformation.map(({ date, occurrenceLabel }) => ( | ||
<div className={`${baseClassName}__occurrence-details-text`} key={String(date)}> | ||
<Text variant={TextVariants.string2}>{occurrenceLabel}</Text> | ||
<Text variant={TextVariants.string2} className={`${baseClassName}__date`}> | ||
{date} | ||
</Text> | ||
</div> | ||
))} | ||
|
||
{isClosed && children} | ||
</div> | ||
{!isClosed && ( | ||
<Button className={`${baseClassName}__cta`} onClick={onClick}> | ||
{ctaLabel} | ||
</Button> | ||
)} | ||
</div> | ||
</PageMargin> | ||
</div> | ||
); | ||
}, | ||
); | ||
|
||
SaleHeaderBanner.displayName = 'SaleHeaderBanner'; | ||
|
||
export default SaleHeaderBanner; |
26 changes: 26 additions & 0 deletions
26
src/patterns/SaleHeaderBanner/SaleHeaderBrowseAuctions.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { ComponentProps, forwardRef } from 'react'; | ||
import { getCommonProps } from '../../utils'; | ||
import { Text, TextVariants } from '../../components/Text'; | ||
import { Link } from '../../components/Link'; | ||
|
||
export interface SaleHeaderBrowseAuctionsProps extends ComponentProps<'div'> { | ||
ctaLabel?: string; | ||
ctaText?: string; | ||
} | ||
|
||
const SaleHeaderBrowseAuctions = forwardRef<HTMLElement, SaleHeaderBrowseAuctionsProps>( | ||
({ ctaText = 'View Calendar', ctaLabel = 'Browse Upcoming Sale', className, ...props }, _ref) => { | ||
const { className: baseClassName } = getCommonProps(props, 'SaleHeaderBanner'); | ||
|
||
return ( | ||
<div className={`${baseClassName}__occurrence-details-text`}> | ||
<Text variant={TextVariants.string2}>{ctaLabel}</Text> | ||
<Link href="/calendar">{ctaText}</Link> | ||
</div> | ||
); | ||
}, | ||
); | ||
|
||
SaleHeaderBrowseAuctions.displayName = 'SaleHeaderBrowseAuctions'; | ||
|
||
export default SaleHeaderBrowseAuctions; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { ComponentProps, forwardRef } from 'react'; | ||
import { getCommonProps } from '../../utils'; | ||
import { Text, TextVariants } from '../../components/Text'; | ||
|
||
export interface SaleHeaderCountdownProps extends ComponentProps<'div'> { | ||
label?: string; | ||
daysLabel?: string; | ||
hoursLabel?: string; | ||
} | ||
|
||
const SaleHeaderCountdown = forwardRef<HTMLDivElement, SaleHeaderCountdownProps>( | ||
({ label = 'Lots Close in', daysLabel = 'Days', hoursLabel = 'Hours', className, ...props }, ref) => { | ||
const { className: baseClassName, ...commonProps } = getCommonProps(props, 'SaleHeaderBanner'); | ||
|
||
return ( | ||
<div | ||
id="PLACEHOLDER FOR TIMER COMPONENT" | ||
className={`${baseClassName}__countdown-container`} | ||
{...commonProps} | ||
{...props} | ||
ref={ref} | ||
> | ||
<Text variant={TextVariants.heading5}>{label}</Text> | ||
<Text variant={TextVariants.heading5}>2 {daysLabel}</Text> | ||
<Text variant={TextVariants.heading5}>17 {hoursLabel}</Text> | ||
</div> | ||
); | ||
}, | ||
); | ||
|
||
SaleHeaderCountdown.displayName = 'SaleHeaderCountdown'; | ||
|
||
export default SaleHeaderCountdown; |
Oops, something went wrong.