Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate Footer, Heading, DonateSection, HeroBanner, and Modal components to Typescript #1805

Merged
merged 9 commits into from
Mar 5, 2024
17 changes: 16 additions & 1 deletion components/Footer/Footer.js → components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,26 @@ import Image from 'next/image';
import Logo from 'public/static/images/logo.svg';
import styles from './Footer.module.css';

export type FooterPropsType = {
/**
* Url string applied ot the link.
*/
href: string;
/**
* String applied to the link label.
*/
name: string;
/**
* Only pass analytics event label if you're href is to an external website
*/
analyticsEventLabel?: string;
};

function Footer() {
const currentYear = new Date().getFullYear();

// eslint-disable-next-line react/prop-types
const renderLink = ({ href, name, analyticsEventLabel }) => {
const renderLink = ({ href, name, analyticsEventLabel }: FooterPropsType) => {
return (
<li key={href}>
{analyticsEventLabel ? (
Expand Down
48 changes: 33 additions & 15 deletions components/Heading/Heading.js → components/Heading/Heading.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,46 @@
import { string, bool, oneOf } from 'prop-types';
import classNames from 'classnames';
import kebabCase from 'lodash/kebabCase';
import ScreenReaderOnly from 'components/ScreenReaderOnly/ScreenReaderOnly';
import LinkIcon from 'static/images/icons/FontAwesome/link-solid.svg';
import styles from './Heading.module.css';

Heading.propTypes = {
className: string,
hasHashLink: bool,
hasTitleUnderline: bool,
headingLevel: oneOf([1, 2, 3, 4, 5, 6]),
text: string.isRequired,
};
type HeadingLevelType = 1 | 2 | 3 | 4 | 5 | 6;

Heading.defaultProps = {
className: undefined,
hasHashLink: true,
hasTitleUnderline: false,
headingLevel: 2,
export type HeadingPropsType = {
/**
* Text to be rendered in the heading element.
*/
text: string;
/**
* Applies classnames to the base `figure` element for styling.
*/
className?: string;
/**
* Applies an anchor as the base element if true.
* @default true
*/
hasHashLink?: boolean;
/**
* Displays an optional line under the title.
* @default false
*/
hasTitleUnderline?: boolean;
/**
* Sets the heading level (h1, h2, etc)
* @default 2
*/
headingLevel?: HeadingLevelType;
};

function Heading({ className, hasHashLink, hasTitleUnderline, headingLevel, text }) {
function Heading({
className,
hasHashLink = true,
hasTitleUnderline = false,
headingLevel = 2,
text,
}: HeadingPropsType) {
const anchorId = `${kebabCase(text)}-link`;
const HeadingElement = `h${headingLevel}`;
const HeadingElement = `h${headingLevel}` as keyof JSX.IntrinsicElements;

return (
<div className={styles.headingContainer}>
Expand Down
22 changes: 0 additions & 22 deletions components/Heading/__stories__/Heading.stories.js

This file was deleted.

18 changes: 18 additions & 0 deletions components/Heading/__stories__/Heading.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Meta, StoryObj } from '@storybook/react';
import Heading from '../Heading';

type HeadingStoryType = StoryObj<typeof Heading>;

const meta: Meta<typeof Heading> = {
title: 'Heading',
component: Heading,
args: {
text: 'Heading text',
},
};

export default meta;

export const Default: HeadingStoryType = {
render: args => <Heading {...args} />,
};
Original file line number Diff line number Diff line change
@@ -1,24 +1,38 @@
import { string, node, bool } from 'prop-types';
import classNames from 'classnames';
import Container from 'components/Container/Container';
import { HERO_BANNER_H1 } from 'common/constants/testIDs';

HeroBanner.propTypes = {
backgroundImageSource: string,
className: string,
children: node,
isFullViewportHeight: bool,
title: string.isRequired,
export type HeroBannerPropsType = {
/**
* Renders a title for the banner.
*/
title: string;
/**
* Sets the path for an optional background image.
*/
backgroundImageSource?: string;
/**
* Applies classnames to the base `figure` element for styling.
*/
className?: string;
/**
* Content to be rendered in the Container.
*/
children?: React.ReactNode;
/**
* Sets the height of the container to be full viewport height.
* @default false
*/
isFullViewportHeight?: boolean;
};

HeroBanner.defaultProps = {
backgroundImageSource: '',
className: undefined,
children: undefined,
isFullViewportHeight: false,
};

function HeroBanner({ backgroundImageSource, children, className, isFullViewportHeight, title }) {
function HeroBanner({
backgroundImageSource,
children,
className,
isFullViewportHeight = false,
title,
}: HeroBannerPropsType) {
return (
<Container
backgroundImageSource={backgroundImageSource}
Expand Down
14 changes: 0 additions & 14 deletions components/HeroBanner/__stories__/HeroBanner.stories.js

This file was deleted.

18 changes: 18 additions & 0 deletions components/HeroBanner/__stories__/HeroBanner.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Meta, StoryObj } from '@storybook/react';
import HeroBanner from '../HeroBanner';

type HeroBannerStoryType = StoryObj<typeof HeroBanner>;

const meta: Meta<typeof HeroBanner> = {
title: 'HeroBanner',
component: HeroBanner,
args: {
title: 'Banner Title',
},
};

export default meta;

export const Default: HeroBannerStoryType = {
render: args => <HeroBanner {...args} />,
};
57 changes: 37 additions & 20 deletions components/Modal/Modal.js → components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,59 @@
import { node, string, bool, func } from 'prop-types';
import classNames from 'classnames';
import * as Dialog from '@radix-ui/react-dialog';
import { gtag } from 'common/utils/thirdParty/gtag';
import CloseButton from 'components/Buttons/CloseButton/CloseButton';
import { MODAL_CONTENT, MODAL_OVERLAY } from 'common/constants/testIDs';

Modal.propTypes = {
children: node.isRequired,
className: string,
isOpen: bool,
onRequestClose: func.isRequired,
screenReaderLabel: string.isRequired, // basically a summarizing title
canClose: bool,
childrenClassName: string,
};

Modal.defaultProps = {
className: undefined,
isOpen: false,
canClose: true,
childrenClassName: undefined,
export type ModalPropsType = {
/**
* Content to be rendered in the modal.
*/
children: React.ReactNode;
/**
* Function that is called when the user clicks the close button.
*/
onRequestClose: (arg1: any) => void;
/**
* Applies a label for the screen reader.
*/
screenReaderLabel: string;
/**
* Applies style classes to the wrapping div.
*/
className?: string;
/**
* Sets if the modal is open an visible (or not)
* @default false
*/
isOpen?: boolean;
/**
* Sets if the modal can be closed by the user
* @default true
*/
canClose?: boolean;
/**
* Applies style classes to the child content.
*/
childrenClassName?: string;
};

function Modal({
children,
className,
isOpen,
isOpen = false,
onRequestClose,
screenReaderLabel,
canClose,
canClose = true,
childrenClassName,
}) {
}: ModalPropsType) {
if (isOpen) {
gtag.modalView(screenReaderLabel);
}

const portalContainer =
typeof window !== 'undefined' ? document.querySelector('#__next') ?? undefined : undefined;
typeof window !== 'undefined'
? (document.querySelector('#__next') as HTMLElement) ?? undefined
: undefined;

return (
<Dialog.Root defaultOpen={false} open={isOpen}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
import { Meta, StoryObj } from '@storybook/react';
import { useState } from 'react';
import isChromatic from 'chromatic/isChromatic';

import { descriptions } from 'common/constants/descriptions';
import Button from 'components/Buttons/Button/Button';
import Modal from '../Modal';

export const Default = {
type ModalStoryType = StoryObj<typeof Modal>;

const meta: Meta<typeof Modal> = {
title: 'Modal',
component: Modal,
parameters: {
previewTabs: {
'storybook/docs/panel': { hidden: true },
},
docs: {
autodocs: false,
disable: true,
page: null,
},
},
};

export default meta;

export const Default: ModalStoryType = {
render: args => {
const [isDemoModalOpen, setIsDemoModalOpen] = useState(args.isOpen);

return (
<>
<Button onClick={() => setIsDemoModalOpen(true)}>Open Modal</Button>
<Button onClick={() => setIsDemoModalOpen(true)}>Open modal</Button>

<Modal
{...args}
Expand All @@ -28,22 +47,8 @@ export const Default = {
},
};

export const NonDismissableModal = {
render: args => {
const [isDemoModalOpen, setIsDemoModalOpen] = useState(args.isOpen);

return (
<>
<Button onClick={() => setIsDemoModalOpen(true)}>Open Modal</Button>

<Modal
{...args}
isOpen={isDemoModalOpen}
onRequestClose={prevValue => setIsDemoModalOpen(!prevValue)}
/>
</>
);
},
export const NonDismissableModal: ModalStoryType = {
...Default,
args: {
canClose: false,
isOpen: false,
Expand All @@ -59,20 +64,3 @@ export const NonDismissableModal = {
screenReaderLabel: 'You have completed the form.',
},
};

const meta = {
title: 'Modal',
component: Modal,
parameters: {
previewTabs: {
'storybook/docs/panel': { hidden: true },
},
docs: {
autodocs: false,
disable: true,
page: null,
},
},
};

export default meta;
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('Modal', () => {

it('should render with many props assigned', () => {
const { container } = render(
<Modal {...requiredProps} className="test-class" isOpen shouldCloseOnOverlayClick={false}>
<Modal {...requiredProps} className="test-class" isOpen>
Test
</Modal>,
);
Expand Down
Loading
Loading