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
90 changes: 90 additions & 0 deletions .changeset/honest-dolphins-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
"@heroui/use-intersection-observer": patch
"@heroui/use-data-scroll-overflow": patch
"@heroui/use-aria-accordion-item": patch
"@heroui/use-aria-modal-overlay": patch
"@heroui/use-safe-layout-effect": patch
"@heroui/use-aria-multiselect": patch
"@heroui/use-infinite-scroll": patch
"@heroui/use-scroll-position": patch
"@heroui/react-rsc-utils": patch
"@heroui/scroll-shadow": patch
"@heroui/use-aria-accordion": patch
"@heroui/autocomplete": patch
"@heroui/number-input": patch
"@heroui/use-update-effect": patch
"@heroui/dom-animation": patch
"@heroui/stories-utils": patch
"@heroui/breadcrumbs": patch
"@heroui/date-picker": patch
"@heroui/use-aria-overlay": patch
"@heroui/use-callback-ref": patch
"@heroui/framer-utils": patch
"@heroui/shared-icons": patch
"@heroui/shared-utils": patch
"@heroui/date-input": patch
"@heroui/pagination": patch
"@heroui/use-aria-button": patch
"@heroui/react-utils": patch
"@heroui/accordion": patch
"@heroui/input-otp": patch
"@heroui/use-disclosure": patch
"@heroui/use-is-mounted": patch
"@heroui/use-pagination": patch
"@heroui/use-real-shape": patch
"@heroui/aria-utils": patch
"@heroui/test-utils": patch
"@heroui/calendar": patch
"@heroui/checkbox": patch
"@heroui/dropdown": patch
"@heroui/progress": patch
"@heroui/skeleton": patch
"@heroui/use-aria-link": patch
"@heroui/use-clipboard": patch
"@heroui/use-draggable": patch
"@heroui/use-is-mobile": patch
"@heroui/use-ref-state": patch
"@heroui/divider": patch
"@heroui/listbox": patch
"@heroui/popover": patch
"@heroui/snippet": patch
"@heroui/spinner": patch
"@heroui/tooltip": patch
"@heroui/avatar": patch
"@heroui/button": patch
"@heroui/drawer": patch
"@heroui/navbar": patch
"@heroui/ripple": patch
"@heroui/select": patch
"@heroui/slider": patch
"@heroui/spacer": patch
"@heroui/switch": patch
"@heroui/use-measure": patch
"@heroui/alert": patch
"@heroui/badge": patch
"@heroui/image": patch
"@heroui/input": patch
"@heroui/modal": patch
"@heroui/radio": patch
"@heroui/table": patch
"@heroui/toast": patch
"@heroui/use-resize": patch
"@heroui/card": patch
"@heroui/chip": patch
"@heroui/code": patch
"@heroui/form": patch
"@heroui/link": patch
"@heroui/menu": patch
"@heroui/tabs": patch
"@heroui/user": patch
"@heroui/system-rsc": patch
"@heroui/use-image": patch
"@heroui/use-theme": patch
"@heroui/kbd": patch
"@heroui/use-ssr": patch
"@heroui/system": patch
"@heroui/react": patch
"@heroui/theme": patch
---

remove RA dependencies (overlays & utils)
4 changes: 1 addition & 3 deletions packages/core/system/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@
"dependencies": {
"@heroui/react-utils": "workspace:*",
"@heroui/system-rsc": "workspace:*",
"@react-aria/i18n": "3.12.10",
"@react-aria/overlays": "3.27.3",
"@react-aria/utils": "3.29.1"
"@react-aria/i18n": "3.12.10"
}
}
114 changes: 114 additions & 0 deletions packages/core/system/src/ext/overlay-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Partial code from react-spectrum to avoid importing the entire package
// ref: packages/@react-aria/overlays/src/useModal.tsx

import type {
AriaAttributes,
AriaRole,
CSSProperties,
DOMAttributes as ReactDOMAttributes,
JSX,
ReactNode,
} from "react";

import React, {useContext, useMemo, useState} from "react";

interface FocusableElement extends Element, HTMLOrSVGElement {}

interface DOMAttributes<T = FocusableElement> extends AriaAttributes, ReactDOMAttributes<T> {
id?: string | undefined;
role?: AriaRole | undefined;
tabIndex?: number | undefined;
style?: CSSProperties | undefined;
className?: string | undefined;
}

interface ModalProviderAria {
modalProviderProps: AriaAttributes;
}

interface ModalContext {
parent: ModalContext | null;
modalCount: number;
addModal: () => void;
removeModal: () => void;
}

export interface ModalProviderProps extends DOMAttributes {
children: ReactNode;
}

const Context = React.createContext<ModalContext | null>(null);

/**
* Each ModalProvider tracks how many modals are open in its subtree. On mount, the modals
* trigger `addModal` to increment the count, and trigger `removeModal` on unmount to decrement it.
* This is done recursively so that all parent providers are incremented and decremented.
* If the modal count is greater than zero, we add `aria-hidden` to this provider to hide its
* subtree from screen readers. This is done using React context in order to account for things
* like portals, which can cause the React tree and the DOM tree to differ significantly in structure.
*/
function ModalProvider(props: ModalProviderProps): JSX.Element {
let {children} = props;
let parent = useContext(Context);
let [modalCount, setModalCount] = useState(0);
let context = useMemo(
() => ({
parent,
modalCount,
addModal() {
setModalCount((count) => count + 1);
if (parent) {
parent.addModal();
}
},
removeModal() {
setModalCount((count) => count - 1);
if (parent) {
parent.removeModal();
}
},
}),
[parent, modalCount],
);

return <Context.Provider value={context}>{children}</Context.Provider>;
}

/**
* Used to determine if the tree should be aria-hidden based on how many
* modals are open.
*/
function useModalProvider(): ModalProviderAria {
let context = useContext(Context);

return {
modalProviderProps: {
"aria-hidden": context && context.modalCount > 0 ? true : undefined,
},
};
}

/**
* Creates a root node that will be aria-hidden if there are other modals open.
*/
function OverlayContainerDOM(props: ModalProviderProps) {
let {modalProviderProps} = useModalProvider();

return <div data-overlay-container {...props} {...modalProviderProps} />;
}

/**
* An OverlayProvider acts as a container for the top-level application.
* Any application that uses modal dialogs or other overlays should
* be wrapped in a `<OverlayProvider>`. This is used to ensure that
* the main content of the application is hidden from screen readers
* if a modal or other overlay is opened. Only the top-most modal or
* overlay should be accessible at once.
*/
export function OverlayProvider(props: ModalProviderProps): JSX.Element {
return (
<ModalProvider>
<OverlayContainerDOM {...props} />
</ModalProvider>
);
}
Loading