Skip to content

[core] Import @floating-ui/react #2002

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

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a08a114
refactor: initial import
romgrk May 30, 2025
714591a
perf: mergeProps
romgrk May 30, 2025
6e77a0d
lint
romgrk May 30, 2025
a66ee2f
lint
romgrk May 30, 2025
94b90d9
lint
romgrk May 30, 2025
77ac4ec
lint
romgrk May 30, 2025
ab4a5e6
refactor: rm useClick, mkdir interactions
romgrk May 30, 2025
43309cc
fix
romgrk May 30, 2025
07acc18
lint
romgrk May 30, 2025
357024d
refactor: use BUI hooks
romgrk May 30, 2025
b4a34ad
refactor: rm log
romgrk May 30, 2025
5072ffc
refactor: safeReact
romgrk May 30, 2025
6a3a244
refactor: merge platform detect code
romgrk May 30, 2025
7c5713f
lint
romgrk May 30, 2025
5d59083
revert: docs draft
romgrk May 30, 2025
dec7604
refactor: FocusGuard
romgrk May 30, 2025
0e4f86f
lint
romgrk May 30, 2025
080dc69
Merge branch 'master' into refactor-floating-ui-react
romgrk May 30, 2025
91a1740
lint
romgrk May 30, 2025
576af67
deps: remove use-isomorphic-layout-effect
romgrk May 30, 2025
386491d
ci: run (empty commit)
romgrk May 30, 2025
f7c633b
test: add useInteractions, useClientPoint
romgrk May 30, 2025
b75795a
lint
romgrk May 30, 2025
5273876
lint
romgrk May 30, 2025
22baeb0
Merge branch 'master' into refactor-floating-ui-react
romgrk May 30, 2025
6493fa1
lint
romgrk May 30, 2025
1356f70
refactor: add tests, move useClick
romgrk May 30, 2025
1ab5b82
lint
romgrk May 30, 2025
314d9f0
lint
romgrk May 30, 2025
59be3cb
lint
romgrk May 30, 2025
2b30b2b
lint
romgrk Jun 3, 2025
7c546c3
fix: import upstream fixes
romgrk Jun 3, 2025
5925241
test: make jsdom work
romgrk Jun 3, 2025
04e3f19
Merge branch 'master' into refactor-floating-ui-react
romgrk Jun 3, 2025
4d3ebb3
lint
romgrk Jun 3, 2025
2899a41
refactor: use BUI timeout utilities
romgrk Jun 3, 2025
281ddcc
lint
romgrk Jun 3, 2025
86956f3
fix: weakmap fix
romgrk Jun 4, 2025
0c635c2
test: add useTypeahead
romgrk Jun 6, 2025
9d4f0b2
Merge branch 'master' into refactor-floating-ui-react
romgrk Jun 6, 2025
d75ca04
lint
romgrk Jun 6, 2025
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
2 changes: 1 addition & 1 deletion docs/reference/generated/toast-root.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "Groups all parts of an individual toast.\nRenders a `<div>` element.",
"props": {
"swipeDirection": {
"type": "'left' | 'right' | 'up' | 'down' | ('left' | 'right' | 'up' | 'down')[]",
"type": "'right' | 'left' | 'up' | 'down' | ('right' | 'left' | 'up' | 'down')[]",
"default": "['down', 'right']",
"description": "Direction(s) in which the toast can be swiped to dismiss."
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,11 @@
"@types/yargs": "^17.0.33",
"@typescript-eslint/eslint-plugin": "^8.33.0",
"@typescript-eslint/parser": "^8.33.0",
"@vvago/vale": "^3.11.2",
"@vitejs/plugin-react": "^4.5.0",
"@vitest/browser": "^3.1.4",
"@vitest/coverage-istanbul": "3.1.4",
"@vitest/ui": "3.1.4",
"@vvago/vale": "^3.11.2",
"babel-loader": "^10.0.0",
"babel-plugin-macros": "^3.1.0",
"babel-plugin-module-resolver": "^5.0.2",
Expand Down
4 changes: 3 additions & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@
},
"dependencies": {
"@babel/runtime": "^7.27.0",
"@floating-ui/react": "^0.27.10",
"@floating-ui/react-dom": "^2.1.2",
"@floating-ui/utils": "^0.2.9",
"tabbable": "^6.2.0",
"reselect": "^5.1.1",
"use-sync-external-store": "^1.5.0"
},
Expand All @@ -102,6 +103,7 @@
"@types/sinon": "^17.0.4",
"@types/use-sync-external-store": "^1.5.0",
"chai": "^4.5.0",
"clsx": "^2.1.1",
"fs-extra": "^11.3.0",
"lodash": "^4.17.21",
"react": "^19.1.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';
import * as React from 'react';
import { FloatingFocusManager } from '@floating-ui/react';
import { FloatingFocusManager } from '../../floating-ui-react';
import { useDialogPopup } from '../../dialog/popup/useDialogPopup';
import { useAlertDialogRootContext } from '../root/AlertDialogRootContext';
import { useRenderElement } from '../../utils/useRenderElement';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';
import * as React from 'react';
import { FloatingPortal } from '@floating-ui/react';
import { FloatingPortal } from '../../floating-ui-react';
import { useAlertDialogRootContext } from '../root/AlertDialogRootContext';
import { AlertDialogPortalContext } from './AlertDialogPortalContext';

Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/composite/composite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export {
getGridNavigatedIndex,
getMaxListIndex,
getMinListIndex,
} from '@floating-ui/react/utils';
} from '../floating-ui-react/utils';

export interface Dimensions {
width: number;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';
import * as React from 'react';
import { contains, getTarget, stopEvent } from '@floating-ui/react/utils';
import { contains, getTarget, stopEvent } from '../../floating-ui-react/utils';
import type { BaseUIComponentProps } from '../../utils/types';
import { useEventCallback } from '../../utils/useEventCallback';
import { useContextMenuRootContext } from '../root/ContextMenuRootContext';
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/dialog/popup/DialogPopup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';
import * as React from 'react';
import { FloatingFocusManager } from '@floating-ui/react';
import { FloatingFocusManager } from '../../floating-ui-react';
import { useDialogPopup } from './useDialogPopup';
import { useDialogRootContext } from '../root/DialogRootContext';
import { useRenderElement } from '../../utils/useRenderElement';
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/dialog/portal/DialogPortal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';
import * as React from 'react';
import { FloatingPortal } from '@floating-ui/react';
import { FloatingPortal } from '../../floating-ui-react';
import { useDialogRootContext } from '../root/DialogRootContext';
import { DialogPortalContext } from './DialogPortalContext';

Expand Down
6 changes: 3 additions & 3 deletions packages/react/src/dialog/root/useDialogRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
import * as React from 'react';
import {
FloatingRootContext,
useClick,
useDismiss,
useFloatingRootContext,
useInteractions,
useRole,
type OpenChangeReason as FloatingUIOpenChangeReason,
} from '@floating-ui/react';
import { getTarget } from '@floating-ui/react/utils';
import { useClick } from '../../utils/floating-ui/useClick';
} from '../../floating-ui-react';
import { getTarget } from '../../floating-ui-react/utils';
import { useControlled } from '../../utils/useControlled';
import { useEventCallback } from '../../utils/useEventCallback';
import { useScrollLock } from '../../utils/useScrollLock';
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/field/label/FieldLabel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';
import * as React from 'react';
import { getTarget } from '@floating-ui/react/utils';
import { getTarget } from '../../floating-ui-react/utils';
import { FieldRoot } from '../root/FieldRoot';
import { useFieldRootContext } from '../root/FieldRootContext';
import { fieldValidityMapping } from '../utils/constants';
Expand Down
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is NextFloatingDelayGroup.

Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import * as React from 'react';
import { useTimeout, Timeout } from '../../utils/useTimeout';
import { useModernLayoutEffect } from '../../utils/useModernLayoutEffect';

import { getDelay } from '../hooks/useHover';
import type { FloatingRootContext, Delay } from '../types';

interface ContextValue {
hasProvider: boolean;
timeoutMs: number;
delayRef: React.MutableRefObject<Delay>;
initialDelayRef: React.MutableRefObject<Delay>;
timeout: Timeout;
currentIdRef: React.MutableRefObject<any>;
currentContextRef: React.MutableRefObject<{
onOpenChange: (open: boolean) => void;
setIsInstantPhase: (value: boolean) => void;
} | null>;
}

const FloatingDelayGroupContext = React.createContext<ContextValue>({
hasProvider: false,
timeoutMs: 0,
delayRef: { current: 0 },
initialDelayRef: { current: 0 },
timeout: new Timeout(),
currentIdRef: { current: null },
currentContextRef: { current: null },
});

export interface FloatingDelayGroupProps {
children?: React.ReactNode;
/**
* The delay to use for the group when it's not in the instant phase.
*/
delay: Delay;
/**
* An optional explicit timeout to use for the group, which represents when
* grouping logic will no longer be active after the close delay completes.
* This is useful if you want grouping to “last” longer than the close delay,
* for example if there is no close delay at all.
*/
timeoutMs?: number;
}

/**
* Experimental next version of `FloatingDelayGroup` to become the default
* in the future. This component is not yet stable.
* Provides context for a group of floating elements that should share a
* `delay`. Unlike `FloatingDelayGroup`, `useDelayGroup` with this
* component does not cause a re-render of unrelated consumers of the
* context when the delay changes.
* @see https://floating-ui.com/docs/FloatingDelayGroup
* @internal
*/
export function FloatingDelayGroup(props: FloatingDelayGroupProps): React.JSX.Element {
const { children, delay, timeoutMs = 0 } = props;

const delayRef = React.useRef(delay);
const initialDelayRef = React.useRef(delay);
const currentIdRef = React.useRef<string | null>(null);
const currentContextRef = React.useRef(null);
const timeout = useTimeout();

return (
<FloatingDelayGroupContext.Provider
value={React.useMemo(
() => ({
hasProvider: true,
delayRef,
initialDelayRef,
currentIdRef,
timeoutMs,
currentContextRef,
timeout,
}),
[timeoutMs, timeout],
)}
>
{children}
</FloatingDelayGroupContext.Provider>
);
}

interface UseDelayGroupOptions {
/**
* Whether delay grouping should be enabled.
* @default true
*/
enabled?: boolean;
}

interface UseDelayGroupReturn {
/**
* The delay reference object.
*/
delayRef: React.MutableRefObject<Delay>;
/**
* Whether animations should be removed.
*/
isInstantPhase: boolean;
/**
* Whether a `<FloatingDelayGroup>` provider is present.
*/
hasProvider: boolean;
}

/**
* Enables grouping when called inside a component that's a child of a
* `FloatingDelayGroup`.
* @see https://floating-ui.com/docs/FloatingDelayGroup
* @internal
*/
export function useDelayGroup(
context: FloatingRootContext,
options: UseDelayGroupOptions = {},
): UseDelayGroupReturn {
const { open, onOpenChange, floatingId } = context;
const { enabled = true } = options;

const groupContext = React.useContext(FloatingDelayGroupContext);
const {
currentIdRef,
delayRef,
timeoutMs,
initialDelayRef,
currentContextRef,
hasProvider,
timeout,
} = groupContext;

const [isInstantPhase, setIsInstantPhase] = React.useState(false);

useModernLayoutEffect(() => {
function unset() {
setIsInstantPhase(false);
currentContextRef.current?.setIsInstantPhase(false);
currentIdRef.current = null;
currentContextRef.current = null;
delayRef.current = initialDelayRef.current;
}

if (!enabled) {
return undefined;
}
if (!currentIdRef.current) {
return undefined;
}

if (!open && currentIdRef.current === floatingId) {
setIsInstantPhase(false);

if (timeoutMs) {
timeout.start(timeoutMs, unset);
return () => {
timeout.clear();
};
}

unset();
}
return undefined;
}, [
enabled,
open,
floatingId,
currentIdRef,
delayRef,
timeoutMs,
initialDelayRef,
currentContextRef,
timeout,
]);

useModernLayoutEffect(() => {
if (!enabled) {
return;
}
if (!open) {
return;
}

const prevContext = currentContextRef.current;
const prevId = currentIdRef.current;

currentContextRef.current = { onOpenChange, setIsInstantPhase };
currentIdRef.current = floatingId;
delayRef.current = {
open: 0,
close: getDelay(initialDelayRef.current, 'close'),
};

if (prevId !== null && prevId !== floatingId) {
timeout.clear();
setIsInstantPhase(true);
prevContext?.setIsInstantPhase(true);
prevContext?.onOpenChange(false);
} else {
setIsInstantPhase(false);
prevContext?.setIsInstantPhase(false);
}
}, [
enabled,
open,
floatingId,
onOpenChange,
currentIdRef,
delayRef,
timeoutMs,
initialDelayRef,
currentContextRef,
timeout,
]);

useModernLayoutEffect(() => {
return () => {
currentContextRef.current = null;
};
}, [currentContextRef]);

return React.useMemo(
() => ({
hasProvider,
delayRef,
isInstantPhase,
}),
[hasProvider, delayRef, isInstantPhase],
);
}
Loading