Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/fancy-mangos-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@easypost/easy-ui": minor
---

feat: support React 19
19 changes: 10 additions & 9 deletions easy-ui-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,36 +35,37 @@
"@react-aria/utils": "^3.32.0",
"@react-stately/toast": "^3.1.2",
"@react-types/shared": "^3.32.1",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.1",
"lodash": "^4.17.21",
"overlayscrollbars": "^2.3.0",
"overlayscrollbars-react": "^0.5.6",
"react-aria": "^3.45.0",
"react-aria-components": "^1.14.0",
"react-is": "^18.3.1",
"react-stately": "^3.43.0",
"react-syntax-highlighter": "^15.6.1",
"react-transition-group": "^4.4.5",
"use-clipboard-copy": "^0.2.0"
},
"peerDependencies": {
"react": "^16.14.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0"
"react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-is": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/lodash": "^4.17.18",
"@types/react-is": "^18.3.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/react-is": "^19.0.0",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/react-transition-group": "^4.4.12",
"@vitejs/plugin-react": "^4.6.0",
"glob": "^10.2.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-is": "^19.0.0",
"sass": "^1.89.2",
"vite-plugin-react-remove-attributes": "^1.0.3",
"vite-plugin-static-copy": "^3.1.2"
Expand Down
7 changes: 5 additions & 2 deletions easy-ui-react/src/CodeBlock/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ReactNode, useMemo } from "react";
import React, { ReactElement, ReactNode, useMemo } from "react";
import { CodeSnippet, CodeSnippetProps } from "../CodeSnippet";
import { SnippetLanguage } from "../CodeSnippet/SyntaxHighlighter";
import { HorizontalStack } from "../HorizontalStack";
Expand Down Expand Up @@ -120,7 +120,10 @@ export function CodeBlock(props: CodeBlockProps) {
const { children, language, onLanguageChange } = props;

const snippets = useMemo(() => {
return filterChildrenByDisplayName(children, "CodeBlock.Snippet");
return filterChildrenByDisplayName(
children,
"CodeBlock.Snippet",
) as ReactElement<CodeSnippetProps>[];
}, [children]);

const headers = useMemo(() => {
Expand Down
3 changes: 2 additions & 1 deletion easy-ui-react/src/CodeBlock/context.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ReactElement, createContext, useContext } from "react";
import { CodeSnippetProps } from "../CodeSnippet";
import { SnippetLanguage } from "../CodeSnippet/SyntaxHighlighter";

export type CodeBlockContextType = {
languages: SnippetLanguage[];
snippet: ReactElement;
snippet: ReactElement<CodeSnippetProps>;
language: SnippetLanguage;
onLanguageChange: (language: SnippetLanguage) => void;
};
Expand Down
2 changes: 1 addition & 1 deletion easy-ui-react/src/ColorPicker/ColorPicker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,6 @@ describe("<ColorPicker />", () => {
expect(screen.getAllByLabelText("Color picker").length).toBeGreaterThan(0);
await userTab(user);
await userKeyboard(user, "{ArrowRight}{ArrowUp}{ArrowLeft}{ArrowDown}");
expect(screen.getByText("hsla(0, 100%, 49.5%, 1)")).toBeInTheDocument();
expect(screen.getByText("hsla(0, 98.02%, 50%, 1)")).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,6 @@ describe("<ColorPickerInputField />", () => {
await userTab(user);
await userKeyboard(user, "{ArrowRight}{ArrowUp}{ArrowLeft}{ArrowDown}");
expect(handleChange).toHaveBeenCalled();
expect(screen.getByLabelText("Primary color")).toHaveValue("#FC0000");
expect(screen.getByLabelText("Primary color")).toHaveValue("#FC0303");
});
});
2 changes: 1 addition & 1 deletion easy-ui-react/src/DataGrid/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function Table<C extends Column, R extends RowType>(
const innerContainerRef = useRef<HTMLDivElement | null>(null);
const tableRef = useRef<HTMLTableElement | null>(null);
const state = useTableState({
...props,
...(props as Parameters<typeof useTableState>[0]),
selectionMode,
selectionBehavior: "toggle",
showSelectionCheckboxes: selectionMode !== "none",
Expand Down
4 changes: 2 additions & 2 deletions easy-ui-react/src/Drawer/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export type DrawerContextType = {
dialogProps: DOMAttributes<FocusableElement>;
titleProps: DOMAttributes<FocusableElement>;
isHeaderStuck: boolean;
bodyRef: RefObject<HTMLDivElement>;
headerInterceptorRef: RefObject<HTMLDivElement>;
bodyRef: RefObject<HTMLDivElement | null>;
headerInterceptorRef: RefObject<HTMLDivElement | null>;
};

type DrawerTriggerContextType = {
Expand Down
4 changes: 2 additions & 2 deletions easy-ui-react/src/Drawer/useIntersectionDetection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { RefObject, useEffect, useState } from "react";
* @returns when element is intersecting
*/
export function useIntersectionDetection(
targetRef: RefObject<HTMLDivElement>,
scrollRef: RefObject<HTMLDivElement>,
targetRef: RefObject<HTMLDivElement | null>,
scrollRef: RefObject<HTMLDivElement | null>,
) {
const [isStuck, setIsStuck] = useState(false);

Expand Down
2 changes: 1 addition & 1 deletion easy-ui-react/src/FormLayout/Section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,6 @@ function findSectionTitleInChildren(children: ReactNode) {
const firstTitleOrSectionType =
firstTitleOrSection.type as NamedExoticComponent;
return firstTitleOrSectionType.displayName === "FormLayout.Title"
? firstTitleOrSection.props.children
? (firstTitleOrSection.props as { children?: ReactNode }).children
: null;
}
10 changes: 8 additions & 2 deletions easy-ui-react/src/Menu/MenuTrigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export type MenuTriggerProps = {
export function MenuTrigger(props: MenuTriggerProps) {
const { children } = props;
const { triggerRef, menuTriggerProps } = useInternalMenuContext();
const clonedProps = mergeProps(menuTriggerProps, children.props);
return React.cloneElement(children, { ...clonedProps, ref: triggerRef });
const clonedProps = mergeProps(
menuTriggerProps,
children.props as Record<string, unknown>,
);
return React.cloneElement(children as ReactElement<Record<string, unknown>>, {
...clonedProps,
ref: triggerRef,
});
}
6 changes: 3 additions & 3 deletions easy-ui-react/src/Modal/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export type ModalContextType = {
titleProps: DOMAttributes<FocusableElement>;
isHeaderStuck: boolean;
isFooterStuck: boolean;
bodyRef: RefObject<HTMLDivElement>;
headerInterceptorRef: RefObject<HTMLDivElement>;
footerInterceptorRef: RefObject<HTMLDivElement>;
bodyRef: RefObject<HTMLDivElement | null>;
headerInterceptorRef: RefObject<HTMLDivElement | null>;
footerInterceptorRef: RefObject<HTMLDivElement | null>;
};

type ModalTriggerContextType = {
Expand Down
4 changes: 2 additions & 2 deletions easy-ui-react/src/Modal/useIntersectionDetection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { RefObject, useEffect, useState } from "react";
* @returns when element is intersecting
*/
export function useIntersectionDetection(
targetRef: RefObject<HTMLDivElement>,
scrollRef: RefObject<HTMLDivElement>,
targetRef: RefObject<HTMLDivElement | null>,
scrollRef: RefObject<HTMLDivElement | null>,
) {
const [isStuck, setIsStuck] = useState(false);

Expand Down
6 changes: 3 additions & 3 deletions easy-ui-react/src/MultiSelect/MultiSelectDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ type MultiSelectDropdownProps<T extends object> = {
children: React.ReactNode | ((item: T) => React.ReactNode);
isLoading?: AsyncListData<T>["isLoading"];
maxItemsUntilScroll?: MenuOverlayProps<T>["maxItemsUntilScroll"];
menuRef: RefObject<HTMLDivElement>;
triggerRef: RefObject<HTMLDivElement>;
menuRef: RefObject<HTMLDivElement | null>;
triggerRef: RefObject<HTMLDivElement | null>;
width: number;
};

Expand Down Expand Up @@ -106,7 +106,7 @@ function ScrollContainer({
scrollRef,
children,
}: {
scrollRef: RefObject<HTMLDivElement>;
scrollRef: RefObject<HTMLDivElement | null>;
children: ReactNode;
}) {
useScrollbar(scrollRef, "ezui-os-theme-overlay");
Expand Down
2 changes: 1 addition & 1 deletion easy-ui-react/src/MultiSelect/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useResizeObserver } from "@react-aria/utils";
import { RefObject, useState } from "react";

export function useElementWidth(elementRef: RefObject<HTMLDivElement>) {
export function useElementWidth(elementRef: RefObject<HTMLDivElement | null>) {
const [width, setWidth] = useState(0);

useResizeObserver({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export type NotificationTransitionProps = {
/**
* Children function that receives a ref to the node being transitioned.
*/
children: ChildrenFunction<{ nodeRef: RefObject<HTMLDivElement> }>;
children: ChildrenFunction<{ nodeRef: RefObject<HTMLDivElement | null> }>;

/**
* Unique key for the transition.
Expand Down
8 changes: 4 additions & 4 deletions easy-ui-react/src/SearchNav/CTAGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ export function CTAGroup(_props: CTAGroupProps) {
<Menu.Overlay placement="bottom right" {...menuOverlayProps}>
<Menu.Section aria-label="Nav actions">
{secondaryCTAItems?.map((item) => {
const itemEle = item as ReactElement;
const itemEle = item as ReactElement<Record<string, unknown>>;
return (
<Menu.Item
key={getFlattenedKey(itemEle.key)}
href={itemEle.props.href}
target={itemEle.props.target}
href={itemEle.props.href as string}
target={itemEle.props.target as string}
>
{itemEle.props.label}
{itemEle.props.label as ReactNode}
</Menu.Item>
);
})}
Expand Down
16 changes: 10 additions & 6 deletions easy-ui-react/src/SearchNav/CondensedSearchNav.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, ReactElement } from "react";
import React, { useState, ReactElement, ReactNode } from "react";
import Close from "@easypost/easy-ui-icons/Close";
import MenuSymbol from "@easypost/easy-ui-icons/Menu";
import Search from "@easypost/easy-ui-icons/Search";
Expand Down Expand Up @@ -67,7 +67,9 @@ export function CondensedSearchNav() {
<Menu.Overlay placement="bottom left" {...menuOverlayProps}>
<Menu.Section aria-label={selectorLabel}>
{selectorChildren?.map((item) => {
const itemEle = item as ReactElement;
const itemEle = item as ReactElement<
Record<string, ReactNode>
>;
return (
<Menu.Item key={getFlattenedKey(itemEle.key)}>
{itemEle.props.children}
Expand All @@ -77,14 +79,16 @@ export function CondensedSearchNav() {
</Menu.Section>
<Menu.Section aria-label="Nav actions">
{secondaryCTAItems?.map((item) => {
const itemEle = item as ReactElement;
const itemEle = item as ReactElement<
Record<string, unknown>
>;
return (
<Menu.Item
key={getFlattenedKey(itemEle.key)}
href={itemEle.props.href}
target={itemEle.props.target}
href={itemEle.props.href as string}
target={itemEle.props.target as string}
>
{itemEle.props.label}
{itemEle.props.label as ReactNode}
</Menu.Item>
);
})}
Expand Down
10 changes: 5 additions & 5 deletions easy-ui-react/src/SearchNav/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function getLogoGroupChildren(logoGroup: ReactNode) {
if (logoGroupDisplayName !== "SearchNav.LogoGroup") {
throw new Error("SearchNav must contain SearchNav.LogoGroup.");
}
const logoGroupElement = logoGroup as ReactElement;
const logoGroupElement = logoGroup as ReactElement<Record<string, ReactNode>>;
const logoGroupChildren = flattenChildren(logoGroupElement.props.children);
const logoDisplayName = getDisplayNameFromReactNode(logoGroupChildren[0]);
if (logoDisplayName !== "SearchNav.Logo") {
Expand All @@ -36,7 +36,7 @@ export function getLogoGroupChildren(logoGroup: ReactNode) {
) === "SearchNav.Selector"
) {
selector = logoGroupChildren[logoGroupChildren.length - 1];
const selectorElem = selector as ReactElement;
const selectorElem = selector as ReactElement<Record<string, ReactNode>>;
selectorChildren = flattenChildren(selectorElem.props.children);
}

Expand All @@ -63,7 +63,7 @@ export function getCTAGroupChildren(ctaGroup: ReactNode) {
let secondaryCTAItems;
let primaryCTAItem;
if (getDisplayNameFromReactNode(ctaGroup) === "SearchNav.CTAGroup") {
const ctaGroupElement = ctaGroup as ReactElement;
const ctaGroupElement = ctaGroup as ReactElement<Record<string, ReactNode>>;
secondaryCTAItems = filterChildrenByDisplayName(
ctaGroupElement.props.children,
"SearchNav.SecondaryCTAItem",
Expand Down Expand Up @@ -92,9 +92,9 @@ export function getCTAGroupChildren(ctaGroup: ReactNode) {
export function getSelectorLabel(selector: ReactNode) {
let selectorLabel;
if (selector) {
const selectorElem = selector as ReactElement;
const selectorElem = selector as ReactElement<Record<string, unknown>>;
const { "aria-label": label } = selectorElem.props;
selectorLabel = label;
selectorLabel = label as string | undefined;
}
return selectorLabel;
}
2 changes: 1 addition & 1 deletion easy-ui-react/src/TabPanels/TabPanelsPanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function TabPanelsPanel({ state, ...props }: TabPanelProps) {
React.cloneElement(children, {
ref,
...tabPanelProps,
...(children as ReactElement).props,
...((children as ReactElement).props as Record<string, unknown>),
})
) : (
<div ref={ref} {...tabPanelProps}>
Expand Down
4 changes: 2 additions & 2 deletions easy-ui-react/src/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ export function Tooltip(props: TooltipProps) {

return (
<>
{React.cloneElement(children, {
...mergeProps(triggerProps, children.props),
{React.cloneElement(children as ReactElement<Record<string, unknown>>, {
...mergeProps(triggerProps, children.props as Record<string, unknown>),
ref: triggerRef,
})}
{tooltipTriggerState.isOpen && (
Expand Down
2 changes: 1 addition & 1 deletion easy-ui-react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ResponsiveProp } from "./utilities/css";
export type DesignTokens = typeof tokens;
export type DesignTokenAliases = keyof DesignTokens;

export type Falsy = boolean | undefined | null | 0;
export type Falsy = boolean | undefined | null | 0 | 0n;

export type Heading = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";

Expand Down
2 changes: 1 addition & 1 deletion easy-ui-react/src/utilities/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function flattenChildren(
acc.push.apply(
acc,
flattenChildren(
node.props.children,
(node.props as { children?: ReactNode }).children,
depth + 1,
keys.concat(node.key || nodeIndex),
),
Expand Down
2 changes: 2 additions & 0 deletions easy-ui-react/src/utilities/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { vi } from "vitest";
declare global {
// eslint-disable-next-line no-var
var jest: object;
// eslint-disable-next-line no-var
var IS_REACT_ACT_ENVIRONMENT: boolean;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion easy-ui-react/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default defineConfig({
removeReactAttributes({
attributes: ["data-testid"],
}),
react({ jsxRuntime: "classic" }),
react({ jsxRuntime: "automatic" }),
viteStaticCopy({
targets: [
{ src: "package.json", dest: ".", transform: cleanPkgJsonForDist },
Expand Down
2 changes: 2 additions & 0 deletions easy-ui-react/vitest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ import {
installScrollToMock,
} from "./src/utilities/test";

globalThis.IS_REACT_ACT_ENVIRONMENT = true;

installJestCompatibleFakeTimers();
installScrollToMock();
Loading
Loading