chore: initial work to migrate to react aria

This commit is contained in:
Corbin Crutchley
2023-07-23 21:55:51 -07:00
parent ea55ef5859
commit 37b4b155b7
7 changed files with 1738 additions and 160 deletions

View File

@@ -33,7 +33,7 @@ module.exports = {
},
},
{
files: ["*.ts"],
files: ["*.ts", "*.tsx"],
parser: "@typescript-eslint/parser",
extends: ["plugin:@typescript-eslint/recommended"],
rules: {

View File

@@ -46,7 +46,13 @@ export default defineConfig({
vite: {
ssr: {
external: ["svgo"],
noExternal: ["@floating-ui/react", "@floating-ui/react-dom"],
noExternal: [
"react-aria",
"react-stately",
/@react-aria/,
/@react-stately/,
/@react-types/,
],
},
plugins: [svgr()],
},

1606
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -117,7 +117,8 @@
"*.{js,ts,astro,jsx}": "prettier --write"
},
"dependencies": {
"@floating-ui/react": "^0.24.3",
"react-aria": "^3.26.0",
"react-stately": "^3.24.0",
"medium-zoom": "^1.0.8",
"preact": "^10.16.0"
},

View File

@@ -53,8 +53,12 @@
}
.iconButton {
height: calc(var(--form-field_padding-vertical) * 2 + var(--p_medium_line-height));
width: calc(var(--form-field_padding-vertical) * 2 + var(--p_medium_line-height));
height: calc(
var(--form-field_padding-vertical) * 2 + var(--p_medium_line-height)
);
width: calc(
var(--form-field_padding-vertical) * 2 + var(--p_medium_line-height)
);
}
.buttonContainer svg {
@@ -63,6 +67,31 @@
flex-grow: 0;
}
.popup > svg {
fill: var(--page-popup_background-color);
.underlay {
position: fixed;
inset: 0;
}
.arrow {
position: absolute;
}
.arrow[data-placement="top"] {
top: 100%;
transform: translateX(-50%);
}
.arrow[data-placement="bottom"] {
bottom: 100%;
transform: translateX(-50%) rotate(180deg);
}
.arrow[data-placement="left"] {
left: 100%;
transform: translateY(-50%) rotate(-90deg);
}
.arrow[data-placement="right"] {
right: 100%;
transform: translateY(-50%) rotate(90deg);
}

View File

@@ -1,17 +1,6 @@
import {
arrow,
FloatingArrow,
FloatingFocusManager,
offset,
useClick,
useDismiss,
useFloating,
useInteractions,
useRole,
} from "@floating-ui/react";
import { useRef, useState } from "preact/hooks";
import { Fragment } from "preact";
import { createPortal, StateUpdater } from "preact/compat";
import { Fragment, RefObject } from "preact";
import { createPortal } from "preact/compat";
import mainStyles from "./pagination.module.scss";
import more from "src/icons/more_horiz.svg?raw";
import { PaginationProps } from "components/pagination/types";
@@ -20,10 +9,19 @@ import { Button, IconOnlyButton } from "components/button/button";
import subtract from "../../icons/subtract.svg?raw";
import add from "../../icons/add.svg?raw";
import { Input } from "components/input/input";
import {
useDialog,
useOverlayTrigger,
usePopover,
Overlay,
DismissButton,
} from "react-aria";
import { OverlayTriggerState, useOverlayTriggerState } from "react-stately";
import { DOMProps } from "@react-types/shared";
function PopupContents(
props: Pick<PaginationProps, "page" | "getPageHref" | "softNavigate"> & {
setIsOpen: StateUpdater<boolean>;
close: () => void;
}
) {
const [count, setCount] = useState(props.page.currentPage);
@@ -35,7 +33,7 @@ function PopupContents(
e.preventDefault();
if (props.softNavigate) {
props.softNavigate(props.getPageHref(count));
props.setIsOpen(false);
props.close();
return;
}
location.href = props.getPageHref(count);
@@ -59,7 +57,7 @@ function PopupContents(
data-testid="pagination-popup-input"
class={style.popupInput}
value={count}
onChange={(e) => {
onInput={(e) => {
const newVal = (e.target as HTMLInputElement).valueAsNumber;
if (newVal > props.page.lastPage) {
setCount(props.page.lastPage);
@@ -97,75 +95,104 @@ function PopupContents(
);
}
interface PaginationPopoverProps
extends Pick<PaginationProps, "page" | "getPageHref" | "softNavigate"> {
triggerRef: RefObject<Element>;
state: OverlayTriggerState;
overlayProps: DOMProps;
}
function PaginationPopover({
triggerRef,
state,
overlayProps,
...props
}: PaginationPopoverProps) {
/* Setup popover */
const popoverRef = useRef(null);
const { popoverProps, underlayProps, arrowProps, placement } = usePopover(
{
shouldFlip: true,
offset: 32 - 14 / 2,
popoverRef,
triggerRef,
},
state
);
/* Setup dialog */
const dialogRef = useRef(null);
const { dialogProps, titleProps } = useDialog(overlayProps, dialogRef);
return (
<Overlay>
<div {...underlayProps} className={style.underlay} />
<div {...popoverProps} ref={popoverRef} className={style.popup}>
<svg
width="24"
height="14"
viewBox="0 0 24 14"
fill="none"
{...arrowProps}
className={style.arrow}
data-placement={placement}
>
<path
d="M9.6 12.8L0 0H24L14.4 12.8C13.2 14.4 10.8 14.4 9.6 12.8Z"
fill="var(--page-popup_background-color)"
/>
<path
d="M2.5 2.08616e-06L11.2 11.6C11.6 12.1333 12.4 12.1333 12.8 11.6L21.5 2.08616e-06L24 0L14.4 12.8C13.2 14.4 10.8 14.4 9.6 12.8L0 2.08616e-06H2.5Z"
fill="var(--page-popup_border-color)"
/>
</svg>
<DismissButton onDismiss={state.close} />
<div {...dialogProps} ref={dialogRef}>
<h1 {...titleProps} className="visually-hidden">
Go to page
</h1>
<PopupContents {...props} close={state.close} />
</div>
<DismissButton onDismiss={state.close} />
</div>
</Overlay>
);
}
export function PaginationMenuAndPopover(
props: Pick<PaginationProps, "page" | "getPageHref" | "softNavigate">
) {
const [isOpen, setIsOpen] = useState(false);
const arrowRef = useRef(null);
const { refs, floatingStyles, context } = useFloating({
open: isOpen,
placement: "top",
onOpenChange: setIsOpen,
middleware: [
offset(32 - 14 / 2),
arrow({
element: arrowRef,
}),
],
});
const click = useClick(context);
const dismiss = useDismiss(context);
const role = useRole(context);
const { getReferenceProps, getFloatingProps } = useInteractions([
click,
dismiss,
role,
]);
const portal = createPortal(
<FloatingFocusManager
context={context}
order={["floating", "content"]}
modal={false}
returnFocus={false}
>
<div
ref={refs.setFloating}
style={floatingStyles as never}
{...getFloatingProps()}
class={style.popup}
>
<PopupContents {...props} setIsOpen={setIsOpen} />
<FloatingArrow
ref={arrowRef}
context={context}
height={14}
width={24}
stroke={"var(--page-popup_border-color)"}
strokeWidth={2}
tipRadius={1.5}
/>
</div>
</FloatingFocusManager>,
document.querySelector("body")
/* Setup trigger */
const triggerRef = useRef(null);
const state = useOverlayTriggerState({});
const { triggerProps, overlayProps } = useOverlayTrigger(
{ type: "dialog" },
state,
triggerRef
);
return (
<Fragment>
<li className={`${mainStyles.paginationItem}`}>
{/* Add onClick since onPress doesn't work with Preact well */}
<button
ref={triggerRef}
onClick={triggerProps.onPress as never}
{...triggerProps}
data-testid="pagination-menu"
ref={refs.setReference}
{...getReferenceProps()}
aria-selected={isOpen}
className={`text-style-body-medium-bold ${mainStyles.extendPageButton} ${mainStyles.paginationButton} ${mainStyles.paginationIconButton}`}
dangerouslySetInnerHTML={{ __html: more }}
/>
</li>
{isOpen && portal}
{state.isOpen && (
<PaginationPopover
{...props}
triggerRef={triggerRef}
state={state}
overlayProps={overlayProps}
/>
)}
</Fragment>
);
}

View File

@@ -31,6 +31,9 @@
list-style: none;
}
.paginationItem {
}
.paginationItemExtra {
display: none;
@@ -61,7 +64,6 @@
box-sizing: border-box;
}
.paginationButton.selected {
background-color: var(--nav-btn_background-color_selected);
color: var(--nav-btn_foreground-color_selected);
@@ -86,7 +88,8 @@
box-sizing: border-box;
}
.paginationButton:disabled, .paginationButton[aria-disabled="true"] {
.paginationButton:disabled,
.paginationButton[aria-disabled="true"] {
background-color: var(--nav-btn_background-color_disabled);
color: var(--nav-btn_foreground-color_disabled);
}