Skip to content

Commit

Permalink
carousel
Browse files Browse the repository at this point in the history
  • Loading branch information
RoyEJohnson committed Dec 23, 2024
1 parent 4ea1375 commit 3ee0788
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 41 deletions.
16 changes: 0 additions & 16 deletions src/app/components/byline/byline.js

This file was deleted.

24 changes: 24 additions & 0 deletions src/app/components/byline/byline.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="byline">
{source ? (
<span className="source">{source}</span>
) : (
<span className="author">{author}</span>
)}
<span className="date">{formatDateForBlog(date)}</span>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -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<HTMLButtonElement>;
hoverText?: string;
disable?: boolean;
}) {
const icon = {
left: faChevronLeft,
Expand All @@ -22,53 +35,53 @@ export function FrameChanger({

return (
<button
type='button'
type="button"
className={`frame-changer ${chevronDirection}`}
onClick={onClick}
disabled={disable}
aria-label={label}
>
<FontAwesomeIcon icon={icon} />
{hoverText && <span className='hover-text'>{hoverText}</span>}
{hoverText && <span className="hover-text">{hoverText}</span>}
</button>
);
}

function HoverText({which, thing}) {
function HoverText({which, thing}: {which: string; thing: string}) {
if (!thing) {
return null;
}
return (
<span className='hover-text'>{`${which} ${thing}`}</span>
);
return <span className="hover-text">{`${which} ${thing}`}</span>;
}

export default function Carousel({
atATime = 1,
children,
hoverTextThing,
ref
}) {
const slides = React.useMemo(
() => {
return React.Children.toArray(children).map((child, i) =>
<CarouselItem index={i} key={i}>{child}</CarouselItem>);
},
[children]
);
}: React.PropsWithChildren<{
atATime?: number;
hoverTextThing: string;
ref?: Parameters<typeof BaseCarousel>[0]['ref'];
}>) {
const slides = React.useMemo(() => {
return React.Children.toArray(children).map((child, i) => (
<CarouselItem index={i} key={i}>
{child}
</CarouselItem>
));
}, [children]);

return (
<BaseCarousel className="carousel" itemsPerPage={atATime} ref={ref}>
<CarouselScroller className="scroller">
{slides}
</CarouselScroller>
<CarouselScroller className="scroller">{slides}</CarouselScroller>
<CarouselButton dir="prev" className="frame-changer left">
<FontAwesomeIcon icon={faChevronLeft} />
<HoverText which='Previous' thing={hoverTextThing} />
<HoverText which="Previous" thing={hoverTextThing} />
</CarouselButton>
<CarouselButton dir="next" className="frame-changer right">
<FontAwesomeIcon icon={faChevronRight} />
<HoverText which='Next' thing={hoverTextThing} />
<HoverText which="Next" thing={hoverTextThing} />
</CarouselButton>
</BaseCarousel>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default function CarouselSection({
minWidth: number;
}>) {
const {innerWidth} = useWindowContext();
const ref = React.useRef<{base: HTMLDivElement}>();
const ref = React.useRef<HTMLDivElement & {base: Element}>(null);
const [atATime, setAtATime] = React.useState(1);

React.useEffect(
Expand Down
2 changes: 1 addition & 1 deletion src/app/pages/webinars/webinar-cards/webinar-grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function PastWebinar({data}: {data: Webinar}) {
return (
<div className='card past'>
<h3>{data.title}</h3>
<Byline author={data.speakers} date={data.start} />
<Byline author={data.speakers} date={data.start.toDateString()} />
<div>{data.description}</div>
<LinkWithChevron href={data.registrationUrl}>
{data.registrationLinkText}
Expand Down
70 changes: 70 additions & 0 deletions test/src/components/carousel.test.tsx
Original file line number Diff line number Diff line change
@@ -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<object>) => mockReactCarousel({children}),
CarouselButton: ({children}: React.PropsWithChildren<object>) => mockCarouselButton({children})
}));

describe('carousel', () => {
mockReactCarousel.mockImplementation(({children}: React.PropsWithChildren<object>) => {
return <div>{children}</div>;
});
mockCarouselButton.mockImplementation(({children}: React.PropsWithChildren<object>) => {
return <div>{children}</div>;
});
it('renders with hover text', () => {
render(
<Carousel hoverTextThing='some hover text' atATime={2}>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
</Carousel>
);
expect(document.querySelectorAll('.hover-text')).toHaveLength(2);
});
it('renders with empty hover text, default atATime', () => {
render(
<Carousel hoverTextThing=''>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
</Carousel>
);
expect(document.querySelectorAll('.hover-text')).toHaveLength(0);
});
});

describe('frame-changer', () => {
const user = userEvent.setup();

it('renders disabled', async () => {
const onclick = jest.fn();

render(<FrameChanger chevronDirection='left' onClick={onclick} disable />);
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(<FrameChanger chevronDirection='left' onClick={onclick} hoverText='some hover text' />);
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');
});
});
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/*"]
}
Expand Down

0 comments on commit 3ee0788

Please sign in to comment.