Skip to content

Port components to ts 5 #2724

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

Merged
merged 9 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 41 additions & 27 deletions src/app/components/body-units/body-units.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import RawHTML from '~/components/jsx-helpers/raw-html';
import Quote from '~/components/quote/quote.js';
import Quote from './quote';
import JITLoad from '~/helpers/jit-load';

function Unknown({data, type}: {data: unknown; type: string}) {
Expand All @@ -19,7 +19,7 @@ type CTAData = {
description: string;
button_href: string;
button_text: string;
}
};

function CTA({data}: {data: CTAData}) {
const alignment = convertAlignment(data.alignment);
Expand All @@ -45,7 +45,7 @@ type AImageData = {
original: {
src: string;
alt: string;
}
};
};
alignment: string;
alt_text: string;
Expand All @@ -71,25 +71,25 @@ function AlignedImage({data}: {data: AImageData}) {
type PQData = {
quote: string;
attribution: string;
}
};

function PullQuote({data}: {data: PQData}) {
const model = {
image: {},
content: data.quote,
attribution: data.attribution
};

return <Quote model={model} />;
}


export type DocumentData = {
download_url: string;
}
};

function Document({data}: {data: DocumentData}) {
return <JITLoad importFn={() => import('./import-pdf-unit.js')} data={data} />;
return (
<JITLoad importFn={() => import('./import-pdf-unit.js')} data={data} />
);
}

// Using CMS tags, which are not camel-case
Expand All @@ -105,25 +105,39 @@ const bodyUnits = {

export type UnitType = {
id: string;
} & ({
type: 'paragraph' | 'aligned_html';
value: string;
} | {
type: 'aligned_image';
value: AImageData;
} | {
type: 'pullquote';
value: PQData;
} | {
type: 'document';
value: DocumentData;
} | {
type: 'blog_cta';
value: CTAData;
})
} & (
| {
type: 'paragraph' | 'aligned_html';
value: string;
}
| {
type: 'aligned_image';
value: AImageData;
}
| {
type: 'pullquote';
value: PQData;
}
| {
type: 'document';
value: DocumentData;
}
| {
type: 'blog_cta';
value: CTAData;
}
);

export default function BodyUnit({unit}: {unit: UnitType}) {
const Unit = bodyUnits[unit.type] as ({data}: {data: typeof unit.value}) => React.JSX.Element;

return Unit ? <Unit data={unit.value} /> : <Unknown data={unit.value} type={unit.type} />;
const Unit = bodyUnits[unit.type] as ({
data
}: {
data: typeof unit.value;
}) => React.JSX.Element;

return Unit ? (
<Unit data={unit.value} />
) : (
<Unknown data={unit.value} type={unit.type} />
);
}
8 changes: 6 additions & 2 deletions src/app/components/body-units/pdf-unit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ function toggleFullscreen(
if (!fsFn) {
return;
}
elem.requestFullscreen = elem[fsFn as keyof typeof elem] as () => Promise<void>;
elem.requestFullscreen = elem[
fsFn as keyof typeof elem
] as () => Promise<void>;

if (!document.fullscreenElement) {
elem.requestFullscreen().catch((err) => {
Expand Down Expand Up @@ -123,7 +125,9 @@ export default function Document({data}: {data: DocumentData}) {
>
<PDF
file={data.download_url}
onLoadSuccess={({numPages: n}: {numPages: number}) => setNumPages(n)}
onLoadSuccess={({numPages: n}: {numPages: number}) =>
setNumPages(n)
}
inputRef={ref}
>
<div className="pages-side-by-side">
Expand Down
22 changes: 22 additions & 0 deletions src/app/components/body-units/quote.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
Copy link
Contributor Author

@RoyEJohnson RoyEJohnson Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was moved from being its own component to being part of body-units, since nothing else used it. Also simplified it a bit, since there was an image feature that wasn't being used, and with that gone, the quote-bucket wrapper wasn't needed.

import RawHTML from '~/components/jsx-helpers/raw-html';

export default function Quote({
model
}: {
model: {
content: string;
attribution: string;
};
}) {
return (
<div className="quote">
<blockquote>
<RawHTML html={model.content} />
{Boolean(model.attribution) && (
<div className="attribution">— {model.attribution}</div>
)}
</blockquote>
</div>
);
}
17 changes: 12 additions & 5 deletions src/app/components/form-select/form-select.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import React from 'react';
import DropdownSelect from '~/components/select/drop-down/drop-down';
import {SelectItem} from '../select/select-context';

export default function FormSelect({
label, name, selectAttributes, options=[], onValueUpdate
label,
name,
selectAttributes,
options = [],
onValueUpdate
}: {
label: string;
name: string;
selectAttributes: object;
options: unknown[];
onValueUpdate?: unknown;
options: SelectItem[];
onValueUpdate?: (v: string) => void;
}) {
return (
<div className="control-group">
{label && <label className="field-label">{label}</label>}
<DropdownSelect
name={name} {...selectAttributes}
options={options} onValueUpdate={onValueUpdate}
name={name}
{...selectAttributes}
options={options}
onValueUpdate={onValueUpdate}
/>
</div>
);
Expand Down
26 changes: 0 additions & 26 deletions src/app/components/paginator/paginator-context.js

This file was deleted.

38 changes: 38 additions & 0 deletions src/app/components/paginator/paginator-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {useState, useCallback} from 'react';
import buildContext from '~/components/jsx-helpers/build-context';

type ContextParams = {
resultsPerPage?: number;
initialPage?: number;
};

function useContextValue(params: ContextParams = {}) {
const {resultsPerPage = 1, initialPage = 1} = params;
const [currentPage, setCurrentPage] = useState(initialPage);
const firstOnPage = (currentPage - 1) * resultsPerPage;
const lastOnPage = firstOnPage + resultsPerPage - 1;
const isVisible = useCallback(
(childIndex: number) =>
childIndex >= firstOnPage && childIndex <= lastOnPage,
[firstOnPage, lastOnPage]
);
const visibleChildren = useCallback(
(children: React.ReactNode[]) =>
children.slice(firstOnPage, lastOnPage + 1),
[firstOnPage, lastOnPage]
);

return {
currentPage,
setCurrentPage,
resultsPerPage,
firstOnPage,
lastOnPage,
isVisible,
visibleChildren
};
}

const {useContext, ContextProvider} = buildContext({useContextValue});

export {useContext as default, ContextProvider as PaginatorContextProvider};
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,28 @@ import React from 'react';
import usePaginatorContext from '../paginator-context';
import './paginator.scss';

function getPageIndicators(pages, currentPage) {
function getPageIndicators(pages: number, currentPage: number) {
const indicatorCount = Math.min(pages, 5);
const propsFor = (label) => ({
const propsFor = (label: number) => ({
label,
page: `page ${label}`,
disabled: label === currentPage,
selected: label === currentPage
});
const result = Array(indicatorCount).fill();
const result = Array(indicatorCount).fill(0);

if (pages - currentPage < 3) {
result[0] = pages - indicatorCount + 1;
} else {
result[0] = (currentPage > 3) ? currentPage - 2 : 1;
result[0] = currentPage > 3 ? currentPage - 2 : 1;
}
for (let i = 1; i < indicatorCount; ++i) {
result[i] = result[i-1] + 1;
result[i] = result[i - 1] + 1;
}
return result.map(propsFor);
}

function PageButtonBar({pages}) {
function PageButtonBar({pages}: {pages: number}) {
const {currentPage, setCurrentPage} = usePaginatorContext();
const disablePrevious = currentPage === 1;
const disableNext = currentPage === pages;
Expand All @@ -37,32 +37,44 @@ function PageButtonBar({pages}) {
}

return (
<nav aria-label='pagination'>
<ul className='no-bullets button-bar'>
<li><button disabled={disablePrevious} onClick={prevPage}>Previous</button></li>
{
pageIndicators.map((indicator) =>
<li key={indicator.page}><button
<nav aria-label="pagination">
<ul className="no-bullets button-bar">
<li>
<button disabled={disablePrevious} onClick={prevPage}>
Previous
</button>
</li>
{pageIndicators.map((indicator) => (
<li key={indicator.page}>
<button
disabled={indicator.disabled}
aria-current={indicator.selected ? 'page' : 'false'}
aria-label={indicator.page}
onClick={() => setCurrentPage(indicator.label)}
>{indicator.label}</button></li>
)
}
<li><button disabled={disableNext} onClick={nextPage}>Next</button></li>
>
{indicator.label}
</button>
</li>
))}
<li>
<button disabled={disableNext} onClick={nextPage}>
Next
</button>
</li>
</ul>
</nav>
);
}

export function PaginatorControls({items}) {
export function PaginatorControls({items}: {items: number}) {
const {currentPage, resultsPerPage} = usePaginatorContext();
const pages = Math.ceil(items / resultsPerPage);
const firstIndex = (currentPage - 1) * resultsPerPage;
const endBefore = Math.min(firstIndex + resultsPerPage, items);
const resultRange = `${firstIndex + 1}-${endBefore}`;
const searchTerm = new window.URLSearchParams(window.location.search).get('q');
const searchTerm = new window.URLSearchParams(window.location.search).get(
'q'
);

return (
<div className="paginator">
Expand Down
Loading