mirror of
https://github.com/LukeHagar/unicorn-utterances.git
synced 2025-12-06 04:21:55 +00:00
chore: initial work to migrate to react aria
This commit is contained in:
@@ -33,7 +33,7 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["*.ts"],
|
||||
files: ["*.ts", "*.tsx"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
extends: ["plugin:@typescript-eslint/recommended"],
|
||||
rules: {
|
||||
|
||||
@@ -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()],
|
||||
},
|
||||
|
||||
1608
package-lock.json
generated
1608
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
},
|
||||
|
||||
@@ -1,68 +1,97 @@
|
||||
@import "src/tokens/index";
|
||||
|
||||
:root {
|
||||
--page-popup_padding: var(--spc-6x);
|
||||
--page-popup_gap: var(--spc-4x);
|
||||
--page-popup_offset: var(--spc-8x);
|
||||
--page-popup_corner-radius: var(--corner-radius_l);
|
||||
--page-popup_border-width: var(--border-width_m);
|
||||
--page-popup_padding: var(--spc-6x);
|
||||
--page-popup_gap: var(--spc-4x);
|
||||
--page-popup_offset: var(--spc-8x);
|
||||
--page-popup_corner-radius: var(--corner-radius_l);
|
||||
--page-popup_border-width: var(--border-width_m);
|
||||
|
||||
--page-popup_background-color: var(--background_primary);
|
||||
--page-popup_border-color: var(--primary_variant);
|
||||
--page-popup_background-color: var(--background_primary);
|
||||
--page-popup_border-color: var(--primary_variant);
|
||||
}
|
||||
|
||||
.popup {
|
||||
background: var(--page-popup_background-color);
|
||||
border: var(--page-popup_border-width) solid var(--page-popup_border-color);
|
||||
box-shadow: var(--shadow_popup_light);
|
||||
padding: var(--page-popup_padding);
|
||||
min-width: 13.5rem;
|
||||
border-radius: var(--page-popup_corner-radius);
|
||||
background: var(--page-popup_background-color);
|
||||
border: var(--page-popup_border-width) solid var(--page-popup_border-color);
|
||||
box-shadow: var(--shadow_popup_light);
|
||||
padding: var(--page-popup_padding);
|
||||
min-width: 13.5rem;
|
||||
border-radius: var(--page-popup_corner-radius);
|
||||
}
|
||||
|
||||
.popupInner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--page-popup_gap);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--page-popup_gap);
|
||||
}
|
||||
|
||||
.popupInput {
|
||||
width: 1px;
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
min-width: calc(3ch + calc(var(--form-field_padding-horizontal) * 2));
|
||||
-moz-appearance: textfield;
|
||||
width: 1px;
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
min-width: calc(3ch + calc(var(--form-field_padding-horizontal) * 2));
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.popupInput::-webkit-outer-spin-button,
|
||||
.popupInput::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.popupTopArea {
|
||||
display: flex;
|
||||
gap: var(--page-popup_gap);
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: var(--page-popup_gap);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
flex-grow: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.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 {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
flex-grow: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user