From 3ee078836ccadf122a9b816b960b992924d735b6 Mon Sep 17 00:00:00 2001 From: Roy Johnson Date: Mon, 23 Dec 2024 13:47:45 -0600 Subject: [PATCH] carousel --- src/app/components/byline/byline.js | 16 ----- src/app/components/byline/byline.tsx | 24 +++++++ .../carousel/{carousel.js => carousel.tsx} | 57 +++++++++------ .../specific/components/carousel-section.tsx | 2 +- .../webinars/webinar-cards/webinar-grid.tsx | 2 +- test/src/components/carousel.test.tsx | 70 +++++++++++++++++++ tsconfig.json | 3 +- 7 files changed, 133 insertions(+), 41 deletions(-) delete mode 100644 src/app/components/byline/byline.js create mode 100644 src/app/components/byline/byline.tsx rename src/app/components/carousel/{carousel.js => carousel.tsx} (51%) create mode 100644 test/src/components/carousel.test.tsx diff --git a/src/app/components/byline/byline.js b/src/app/components/byline/byline.js deleted file mode 100644 index 0eb820402..000000000 --- a/src/app/components/byline/byline.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import './byline.scss'; -import {formatDateForBlog} from '~/helpers/data'; - -export default function Byline({date, author, source=undefined}) { - return ( -
- { - source ? - {source} : - {author} - } - {formatDateForBlog(date)} -
- ); -} diff --git a/src/app/components/byline/byline.tsx b/src/app/components/byline/byline.tsx new file mode 100644 index 000000000..c94380c92 --- /dev/null +++ b/src/app/components/byline/byline.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import './byline.scss'; +import {formatDateForBlog} from '~/helpers/data'; + +export default function Byline({ + date, + author, + source +}: { + date: string; + author: string; + source?: string; +}) { + return ( +
+ {source ? ( + {source} + ) : ( + {author} + )} + {formatDateForBlog(date)} +
+ ); +} diff --git a/src/app/components/carousel/carousel.js b/src/app/components/carousel/carousel.tsx similarity index 51% rename from src/app/components/carousel/carousel.js rename to src/app/components/carousel/carousel.tsx index 30d463de6..49fa69f59 100644 --- a/src/app/components/carousel/carousel.js +++ b/src/app/components/carousel/carousel.tsx @@ -1,15 +1,28 @@ import React from 'react'; -import {Carousel as BaseCarousel, CarouselButton, CarouselItem, CarouselScroller} from 'react-aria-carousel'; +import { + Carousel as BaseCarousel, + CarouselButton, + CarouselItem, + CarouselScroller +} from 'react-aria-carousel'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faChevronLeft} from '@fortawesome/free-solid-svg-icons/faChevronLeft'; import {faChevronRight} from '@fortawesome/free-solid-svg-icons/faChevronRight'; import './carousel.scss'; +// FrameChanger was part of the homegrown carousel, but react-aria-carousel has such items +// built-in, so this is only used by pdf-unit (body-units) now. Might be able to use +// CarouselButton (from react-aria-carousel) for that. export function FrameChanger({ chevronDirection, onClick, - hoverText = '', - disable = false + hoverText, + disable +}: { + chevronDirection: 'left' | 'right'; + onClick: React.MouseEventHandler; + hoverText?: string; + disable?: boolean; }) { const icon = { left: faChevronLeft, @@ -22,25 +35,23 @@ export function FrameChanger({ return ( ); } -function HoverText({which, thing}) { +function HoverText({which, thing}: {which: string; thing: string}) { if (!thing) { return null; } - return ( - {`${which} ${thing}`} - ); + return {`${which} ${thing}`}; } export default function Carousel({ @@ -48,27 +59,29 @@ export default function Carousel({ children, hoverTextThing, ref -}) { - const slides = React.useMemo( - () => { - return React.Children.toArray(children).map((child, i) => - {child}); - }, - [children] - ); +}: React.PropsWithChildren<{ + atATime?: number; + hoverTextThing: string; + ref?: Parameters[0]['ref']; +}>) { + const slides = React.useMemo(() => { + return React.Children.toArray(children).map((child, i) => ( + + {child} + + )); + }, [children]); return ( - - {slides} - + {slides} - + - + ); diff --git a/src/app/pages/subjects/new/specific/components/carousel-section.tsx b/src/app/pages/subjects/new/specific/components/carousel-section.tsx index 5a4da5665..ec6cdfe35 100644 --- a/src/app/pages/subjects/new/specific/components/carousel-section.tsx +++ b/src/app/pages/subjects/new/specific/components/carousel-section.tsx @@ -14,7 +14,7 @@ export default function CarouselSection({ minWidth: number; }>) { const {innerWidth} = useWindowContext(); - const ref = React.useRef<{base: HTMLDivElement}>(); + const ref = React.useRef(null); const [atATime, setAtATime] = React.useState(1); React.useEffect( diff --git a/src/app/pages/webinars/webinar-cards/webinar-grid.tsx b/src/app/pages/webinars/webinar-cards/webinar-grid.tsx index 656611cb9..c2652eb4c 100644 --- a/src/app/pages/webinars/webinar-cards/webinar-grid.tsx +++ b/src/app/pages/webinars/webinar-cards/webinar-grid.tsx @@ -28,7 +28,7 @@ function PastWebinar({data}: {data: Webinar}) { return (

{data.title}

- +
{data.description}
{data.registrationLinkText} diff --git a/test/src/components/carousel.test.tsx b/test/src/components/carousel.test.tsx new file mode 100644 index 000000000..fdedddf28 --- /dev/null +++ b/test/src/components/carousel.test.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import {render, screen} from '@testing-library/preact'; +import userEvent from '@testing-library/user-event'; +import Carousel, { FrameChanger } from '~/components/carousel/carousel'; + +const mockReactCarousel = jest.fn(); +const mockCarouselButton = jest.fn(); + +// react-aria-carousel does not play nice with Jest +jest.mock('react-aria-carousel', () => ({ + Carousel: ({children}: React.PropsWithChildren) => mockReactCarousel({children}), + CarouselButton: ({children}: React.PropsWithChildren) => mockCarouselButton({children}) +})); + +describe('carousel', () => { + mockReactCarousel.mockImplementation(({children}: React.PropsWithChildren) => { + return
{children}
; + }); + mockCarouselButton.mockImplementation(({children}: React.PropsWithChildren) => { + return
{children}
; + }); + it('renders with hover text', () => { + render( + +
one
+
two
+
three
+
four
+
+ ); + expect(document.querySelectorAll('.hover-text')).toHaveLength(2); + }); + it('renders with empty hover text, default atATime', () => { + render( + +
one
+
two
+
three
+
four
+
+ ); + expect(document.querySelectorAll('.hover-text')).toHaveLength(0); + }); +}); + +describe('frame-changer', () => { + const user = userEvent.setup(); + + it('renders disabled', async () => { + const onclick = jest.fn(); + + render(); + const b = screen.getByRole('button'); + + expect(b.getAttribute('disabled')).toBe(""); + await user.click(b); + expect(onclick).not.toHaveBeenCalled(); + }); + it('handles clicks when not disabled', async () => { + const onclick = jest.fn(); + + render(); + const b = screen.getByRole('button'); + + expect(b.getAttribute('disabled')).not.toBe(true); + await user.click(b); + expect(onclick).toHaveBeenCalled(); + expect(b.querySelector('.hover-text')?.textContent).toBe('some hover text'); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index b0fb9327a..c71c65cb1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,11 +3,12 @@ "allowJs": true, // "checkJs": true, "target": "es6", - "module": "commonjs", + "module": "ES2020", "jsx": "react", "outDir": "./dist", "strict": true, "esModuleInterop": true, + "moduleResolution": "bundler", "paths": { "~*": ["./src/app/*"] }