Skip to content

Commit

Permalink
feat: Add internal news carousel for news and announcements page (#744)
Browse files Browse the repository at this point in the history
* Change current news path to news-announcments

* Update test

* Add NewsCarousel and NewsCarouselItem components

* Update storybook component

* Add CTA

* Add query for carousel articles

* Add NewsCarousel component to page

* Add custom ellipses and custom arrows

* Update styles

* Move class to module

* fix: resolve type error from useRef return

See for where the solution came from DefinitelyTyped/DefinitelyTyped#35572 (comment)

* test: add article mock to NewsAnnouncements component

* fix a11y violation in carousel, update mock in test

* Style updates

* Add test

* Add test

* Add news page from main

* Add view older internal news link

* Add comment

* Update styles

* Add conditional render for link

* Remove unnecessary importants

* Update default image

* Update tests

* Remove flex styles

* Add comment

Co-authored-by: John Gedeon <john@truss.works>
Co-authored-by: Abigail Young <abbyoung@gmail.com>
  • Loading branch information
3 people committed Aug 23, 2022
1 parent 344b023 commit c4acfe6
Show file tree
Hide file tree
Showing 18 changed files with 692 additions and 9 deletions.
194 changes: 194 additions & 0 deletions public/assets/images/Seal_of_the_United_States_Space_Force.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 8 additions & 4 deletions src/__fixtures__/data/cmsPortalNewsArticles.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,42 @@
export const cmsPortalNewsArticlesMock = [
{
__typename: 'Article',
id: 'cl3buldda0247riyt6h85cpgc',
id: 'id1',
slug: 'announcing-the-dev-blog',
title: 'Announcing the dev blog',
preview: 'This is the preview text!',
publishedDate: '2022-05-18T17:18:40.802Z',
labels: [],
},
{
__typename: 'Article',
id: 'cl3bpuzvq0885i6ytzr2oumu9',
id: 'id2',
slug: 'welcome-and-overview',
title: 'Welcome and Overview',
preview:
'The USSF/CTIO team has been busy in 2021! This year, we’ve conducted more than 40 one-on-one interviews with Guardians (and have surveyed dozens more) in order to custom create a portal that best serves your needs.',
publishedDate: '2022-05-18T15:06:00.056Z',
labels: [],
},
{
__typename: 'Article',
id: 'cl3bpu9pm0725i6yto1qd5swv',
id: 'id3',
slug: 'version-300-released',
title: 'Version 3.0.0 released!',
preview:
'This release removes the beta gate and officially marks the launch of the new USSF portal!',
publishedDate: '2022-05-18T15:05:27.289Z',
labels: [],
},
{
__typename: 'Article',
id: 'cl3bprjlw0561i6ytphwqi3tb',
id: 'id4',
slug: 'version-285-released-includes-mvp-search-experience-and-a-way-to-filter-the-news',
title:
'Version 2.8.5 released! Includes MVP search experience and a way to filter the news.',
preview:
'Vestibulum in turpis vitae arcu tincidunt maximus sit amet suscipit justo. Morbi lobortis posuere mollis. Suspendisse egestas egestas sapien eu blandit. In euismod suscipit nisi, eget vulputate tellus. Cras vel nisi nec urna facilisis luctus. Phasellus vel sagittis lacus. Ut dapibus ipsum arcu, nec semper ipsum malesuada in. Aliquam et lectus pharetra, gravida eros suscipit, tincidunt libero. Fusce vel ultrices tellus, vel pulvinar diam. Vestibulum pharetra vehicula lacinia.',
publishedDate: '2022-05-18T15:03:24.089Z',
labels: [],
},
]
13 changes: 11 additions & 2 deletions src/__tests__/pages/news-announcements.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import axios from 'axios'
import { renderWithAuth } from '../../testHelpers'

import { cmsAnnouncementsMock } from '../../__fixtures__/data/cmsAnnouncments'
import { cmsPortalNewsArticlesMock } from '../../__fixtures__/data/cmsPortalNewsArticles'
import mockRssFeed from '__mocks__/news-rss'
import '../../__mocks__/mockMatchMedia'
import NewsAnnouncements from 'pages/news-announcements'
Expand Down Expand Up @@ -58,7 +59,10 @@ describe('News page', () => {
})

renderWithAuth(
<NewsAnnouncements announcements={cmsAnnouncementsMock} />,
<NewsAnnouncements
announcements={cmsAnnouncementsMock}
articles={cmsPortalNewsArticlesMock}
/>,
{ user: null }
)
})
Expand All @@ -85,7 +89,12 @@ describe('News page', () => {
return Promise.resolve({ data: mockRssFeed })
})

renderWithAuth(<NewsAnnouncements announcements={cmsAnnouncementsMock} />)
renderWithAuth(
<NewsAnnouncements
announcements={cmsAnnouncementsMock}
articles={cmsPortalNewsArticlesMock}
/>
)

// Slider component in react-slick clones each item in the carousel,
// so a length of 2 is accurate
Expand Down
15 changes: 15 additions & 0 deletions src/components/AnnouncementCarousel/AnnouncementCarousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ import 'slick-carousel/slick/slick.css'
import 'slick-carousel/slick/slick-theme.css'
import { AnnouncementRecord } from 'types'

const CustomEllipse = ({ onClick }: any) => {
return (
<div className="announcement-carousel-container">
<button
onClick={onClick}
type="button"
title="slide"
className={styles.carouselEllipse}
/>
</div>
)
}

const NextArrow = ({ onClick }: any) => {
return (
<div className={styles.carouselArrow}>
Expand Down Expand Up @@ -38,13 +51,15 @@ const AnnouncementCarousel = ({
slidesToScroll: 1,
nextArrow: <NextArrow />,
prevArrow: <PrevArrow />,
dotsClass: `slick-dots ${styles.dots}`,
appendDots: (dots: React.ReactNode) => {
return (
<div style={{ bottom: '-20px' }}>
<ul style={{ margin: '0px', paddingLeft: '0px' }}> {dots} </ul>
</div>
)
},
customPaging: () => <CustomEllipse />,
}

return (
Expand Down
11 changes: 11 additions & 0 deletions src/components/ArticleListItem/ArticleListItem.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,15 @@ describe('ArticleListItem component', () => {
expect(await axe(container)).toHaveNoViolations()
})
})

it('renders the article preview of an rss article', () => {
render(<ArticleListItem article={rssTestArticle} />)

expect(screen.getByText('Aug')).toBeInTheDocument()
expect(screen.getByText('01')).toBeInTheDocument()

expect(screen.getAllByText(rssTestArticle.title)).toHaveLength(1)
expect(screen.getByText(rssTestArticle.preview)).toBeInTheDocument()
expect(screen.getByText(rssTestArticle.sourceName)).toBeInTheDocument()
})
})
40 changes: 40 additions & 0 deletions src/components/NewsCarousel/NewsCarousel.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@import 'styles/uswdsDependencies';
@import 'styles/sfds/sfdsDependencies';

.carouselContainer {
width: 100%;
background-color: #161f2f;
border-radius: 4px;

.dots {
button.newsCarouselEllipse {
width: 16.17px;
height: 16px;
border-radius: 50px;
background: $theme-spacebase-lightest;
}

button::before {
color: transparent;
}
}

> :global(.slick-dots
li.slick-active
div.news-carousel-container
button::before) {
color: transparent;
background: #112548;
width: 16.17px;
height: 16px;
border-radius: 50px;
}
}

.carouselArrow {
background: transparent;
border: none;
&:hover {
cursor: pointer;
}
}
50 changes: 50 additions & 0 deletions src/components/NewsCarousel/NewsCarousel.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react'
import { Meta } from '@storybook/react'
import NewsCarousel from './NewsCarousel'
import { ArticleListItemRecord } from 'types'

export default {
title: 'Components/NewsCarousel',
component: NewsCarousel,
decorators: [
(Story) => (
<div className="sfds">
<Story />
</div>
),
],
} as Meta

const mockArticles: ArticleListItemRecord[] = [
{
id: '1',
title: 'My Thing!',
labels: [
{
id: 'label1',
name: 'A Label',
type: 'Source',
},
],
publishedDate: 'Aug 03, 2022',
preview: "It's a thing",
},
{
id: '2',
title: 'Next USSF town hall happening 1 MAY 2022, at 1300 GMT',
labels: [
{
id: 'label2',
name: 'Super Cool Label',
type: 'Audience',
},
],
publishedDate: 'Aug 03, 2022',
preview:
'The second Inspector General Independent Racial Disparity Review focused on gender and ethnicity, and included additional racial groups (Hispanics, Latinos, Asians, American Indians, Alaska Natives, Native Hawaiians and other Pacific Islanders). It also referenced and compared data from the prior report on racial disparity, involving Black/African American Airmen and Guardians.',
},
]

export const DefaultNewsCarousel = () => (
<NewsCarousel articles={mockArticles} />
)
25 changes: 25 additions & 0 deletions src/components/NewsCarousel/NewsCarousel.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @jest-environment jsdom
*/

import { fireEvent, render, screen } from '@testing-library/react'
import React from 'react'
import { cmsPortalNewsArticlesMock } from '../../__fixtures__/data/cmsPortalNewsArticles'
import '../../__mocks__/mockMatchMedia'
import NewsCarousel from './NewsCarousel'

describe('NewsCarousel component', () => {
it('renders the component with mock articles', () => {
render(<NewsCarousel articles={cmsPortalNewsArticlesMock} />)

const prevButton = screen.getByTestId('slick-prev')
const nextButton = screen.getByTestId('slick-next')

expect(prevButton).toBeInTheDocument()
expect(nextButton).toBeInTheDocument()

// TODO: verify that this actually changes the current slide in the carousel
fireEvent.click(prevButton)
fireEvent.click(nextButton)
})
})
92 changes: 92 additions & 0 deletions src/components/NewsCarousel/NewsCarousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import React, { useRef } from 'react'
import Slider from 'react-slick'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import styles from './NewsCarousel.module.scss'
import NewsCarouselItem from 'components/NewsCarouselItem/NewsCarouselItem'
import 'slick-carousel/slick/slick.css'
import 'slick-carousel/slick/slick-theme.css'
import { ArticleListItemRecord } from 'types'

const CustomEllipse = ({ onClick }: any) => {
return (
<div className="news-carousel-container">
<button
onClick={onClick}
type="button"
title="slide"
className={styles.newsCarouselEllipse}
/>
</div>
)
}

const NewsCarousel = ({ articles }: { articles: ArticleListItemRecord[] }) => {
const sliderRef = useRef<Slider>(null)

const settings = {
dots: true,
accessibility: true,
adaptiveHeight: true,
infinite: true,
speed: 500,
slidesToShow: 1,
slidesToScroll: 1,
arrows: false,
dotsClass: `slick-dots ${styles.dots}`,
appendDots: (dots: React.ReactNode) => {
return (
<div
style={{
bottom: '-40px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
paddingLeft: '8rem',
paddingRight: '8rem',
}}>
<button
type="button"
className={styles.carouselArrow}
data-testid="slick-prev"
onClick={() => {
if (sliderRef.current) {
return sliderRef.current.slickPrev()
}
}}>
<FontAwesomeIcon size="lg" icon="angle-left" />
</button>
<ul
style={{
margin: '0px',
paddingLeft: '0px',
}}>
{' '}
{dots}{' '}
</ul>
<button
type="button"
className={styles.carouselArrow}
data-testid="slick-next"
onClick={() => {
if (sliderRef.current) {
return sliderRef.current.slickNext()
}
}}>
<FontAwesomeIcon size="lg" icon="angle-right" />
</button>
</div>
)
},
customPaging: () => <CustomEllipse />,
}

return (
<Slider ref={sliderRef} className={styles.carouselContainer} {...settings}>
{articles.map((article: ArticleListItemRecord, index: number) => {
return <NewsCarouselItem key={index} article={article} />
})}
</Slider>
)
}

export default NewsCarousel
Loading

0 comments on commit c4acfe6

Please sign in to comment.