diff --git a/package.json b/package.json index c5a007afbcd..094cfb25497 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "@emotion/react": "11.9.3", "@emotion/server": "11.4.0", "@emotion/styled": "^11.10.0", - "@floating-ui/react-dom-interactions": "0.6.6", + "@floating-ui/react-dom-interactions": "^0.10.1", "@radix-ui/react-scroll-area": "1.0.0", "@tabler/icons": "^1.68.0", "clsx": "1.1.1", diff --git a/src/mantine-core/package.json b/src/mantine-core/package.json index 259ed2b56ca..0777b770cac 100644 --- a/src/mantine-core/package.json +++ b/src/mantine-core/package.json @@ -36,7 +36,7 @@ "@mantine/styles": "5.4.0", "@radix-ui/react-scroll-area": "1.0.0", "react-textarea-autosize": "8.3.4", - "@floating-ui/react-dom-interactions": "0.6.6" + "@floating-ui/react-dom-interactions": "^0.10.1" }, "devDependencies": {} } diff --git a/src/mantine-core/src/Floating/FloatingArrow/FloatingArrow.tsx b/src/mantine-core/src/Floating/FloatingArrow/FloatingArrow.tsx index fba138f9002..320cf49166d 100644 --- a/src/mantine-core/src/Floating/FloatingArrow/FloatingArrow.tsx +++ b/src/mantine-core/src/Floating/FloatingArrow/FloatingArrow.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { forwardRef } from 'react'; import { useMantineTheme } from '@mantine/styles'; import { getArrowPositionStyles } from './get-arrow-position-styles'; import { FloatingPosition } from '../types'; @@ -8,35 +8,35 @@ interface FloatingArrowProps extends React.ComponentPropsWithoutRef<'div'> { position: FloatingPosition; arrowSize: number; arrowOffset: number; + arrowX: number; + arrowY: number; visible: boolean; } -export function FloatingArrow({ - withBorder, - position, - arrowSize, - arrowOffset, - visible, - ...others -}: FloatingArrowProps) { - const theme = useMantineTheme(); +export const FloatingArrow = forwardRef( + ({ withBorder, position, arrowSize, arrowOffset, visible, arrowX, arrowY, ...others }, ref) => { + const theme = useMantineTheme(); - if (!visible) { - return null; - } + if (!visible) { + return null; + } - return ( -
- ); -} + return ( +
+ ); + } +); FloatingArrow.displayName = '@mantine/core/FloatingArrow'; diff --git a/src/mantine-core/src/Floating/FloatingArrow/get-arrow-position-styles.ts b/src/mantine-core/src/Floating/FloatingArrow/get-arrow-position-styles.ts index c1720108465..765c878d5df 100644 --- a/src/mantine-core/src/Floating/FloatingArrow/get-arrow-position-styles.ts +++ b/src/mantine-core/src/Floating/FloatingArrow/get-arrow-position-styles.ts @@ -2,11 +2,11 @@ import type { FloatingPosition, FloatingSide, FloatingPlacement } from '../types function horizontalSide( placement: FloatingPlacement | 'center', - arrowSize: number, + arrowY: number, arrowOffset: number ) { if (placement === 'center') { - return { top: `calc(50% - ${arrowSize / 2}px)` }; + return { top: arrowY }; } if (placement === 'end') { @@ -22,12 +22,12 @@ function horizontalSide( function verticalSide( placement: FloatingPlacement | 'center', - arrowSize: number, + arrowX: number, arrowOffset: number, dir: 'rtl' | 'ltr' ) { if (placement === 'center') { - return { left: `calc(50% - ${arrowSize / 2}px)` }; + return { [dir === 'ltr' ? 'left' : 'right']: arrowX }; } if (placement === 'end') { @@ -46,12 +46,16 @@ export function getArrowPositionStyles({ withBorder, arrowSize, arrowOffset, + arrowX, + arrowY, dir, }: { position: FloatingPosition; withBorder: boolean; arrowSize: number; arrowOffset: number; + arrowX: number; + arrowY: number; dir: 'rtl' | 'ltr'; }) { const [side, placement = 'center'] = position.split('-') as [FloatingSide, FloatingPlacement]; @@ -67,7 +71,7 @@ export function getArrowPositionStyles({ if (side === 'left') { return { ...baseStyles, - ...horizontalSide(placement, arrowSize, arrowOffset), + ...horizontalSide(placement, arrowY, arrowOffset), right: arrowPosition, borderLeft: 0, borderBottom: 0, @@ -77,7 +81,7 @@ export function getArrowPositionStyles({ if (side === 'right') { return { ...baseStyles, - ...horizontalSide(placement, arrowSize, arrowOffset), + ...horizontalSide(placement, arrowY, arrowOffset), left: arrowPosition, borderRight: 0, borderTop: 0, @@ -87,7 +91,7 @@ export function getArrowPositionStyles({ if (side === 'top') { return { ...baseStyles, - ...verticalSide(placement, arrowSize, arrowOffset, dir), + ...verticalSide(placement, arrowX, arrowOffset, dir), bottom: arrowPosition, borderTop: 0, [dir === 'ltr' ? 'borderLeft' : 'borderRight']: 0, @@ -97,7 +101,7 @@ export function getArrowPositionStyles({ if (side === 'bottom') { return { ...baseStyles, - ...verticalSide(placement, arrowSize, arrowOffset, dir), + ...verticalSide(placement, arrowX, arrowOffset, dir), top: arrowPosition, borderBottom: 0, [dir === 'ltr' ? 'borderRight' : 'borderLeft']: 0, diff --git a/src/mantine-core/src/Popover/Popover.context.ts b/src/mantine-core/src/Popover/Popover.context.ts index 543b9808247..ec633afbd7f 100644 --- a/src/mantine-core/src/Popover/Popover.context.ts +++ b/src/mantine-core/src/Popover/Popover.context.ts @@ -9,6 +9,9 @@ import { PopoverWidth } from './Popover.types'; interface PopoverContext { x: number; y: number; + arrowX: number; + arrowY: number; + arrowRef: React.RefObject; opened: boolean; transition: MantineTransition; transitionDuration: number; diff --git a/src/mantine-core/src/Popover/Popover.story.tsx b/src/mantine-core/src/Popover/Popover.story.tsx index 3a96e74a824..0acd5932758 100644 --- a/src/mantine-core/src/Popover/Popover.story.tsx +++ b/src/mantine-core/src/Popover/Popover.story.tsx @@ -20,6 +20,20 @@ export function Uncontrolled() { ); } +export function WithArrow() { + return ( +
+ + + + + + Dropdown with arrow + +
+ ); +} + export function Usage() { const [opened, setState] = useState(false); diff --git a/src/mantine-core/src/Popover/Popover.tsx b/src/mantine-core/src/Popover/Popover.tsx index 2dc414ec8ec..32ee62fcc47 100644 --- a/src/mantine-core/src/Popover/Popover.tsx +++ b/src/mantine-core/src/Popover/Popover.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/no-unused-prop-types */ -import React from 'react'; +import React, { useRef } from 'react'; import { useId, useClickOutside } from '@mantine/hooks'; import { useMantineTheme, @@ -138,6 +138,7 @@ const defaultProps: Partial = { }; export function Popover(props: PopoverProps) { + const arrowRef = useRef(null); const { children, position, @@ -181,6 +182,7 @@ export function Popover(props: PopoverProps) { width, position: getFloatingPosition(theme.dir, position), offset: offset + (withArrow ? arrowSize / 2 : 0), + arrowRef, onPositionChange, positionDependencies, opened, @@ -209,7 +211,10 @@ export function Popover(props: PopoverProps) { floating: popover.floating.floating, x: popover.floating.x, y: popover.floating.y, + arrowX: popover.floating?.middlewareData?.arrow?.x, + arrowY: popover.floating?.middlewareData?.arrow?.y, opened: popover.opened, + arrowRef, transition, transitionDuration, exitTransitionDuration, diff --git a/src/mantine-core/src/Popover/PopoverDropdown/PopoverDropdown.tsx b/src/mantine-core/src/Popover/PopoverDropdown/PopoverDropdown.tsx index 3f78134205c..6465310de83 100644 --- a/src/mantine-core/src/Popover/PopoverDropdown/PopoverDropdown.tsx +++ b/src/mantine-core/src/Popover/PopoverDropdown/PopoverDropdown.tsx @@ -73,6 +73,9 @@ export function PopoverDropdown({ style, className, children, ...others }: Popov {children} ; } function getPopoverMiddlewares(options: UsePopoverOptions) { @@ -35,6 +37,8 @@ function getPopoverMiddlewares(options: UsePopoverOptions) { middlewares.push(flip()); } + middlewares.push(arrow({ element: options.arrowRef })); + return middlewares; } diff --git a/src/mantine-core/src/Tooltip/Tooltip.story.tsx b/src/mantine-core/src/Tooltip/Tooltip.story.tsx index b17414082db..503f56f296b 100644 --- a/src/mantine-core/src/Tooltip/Tooltip.story.tsx +++ b/src/mantine-core/src/Tooltip/Tooltip.story.tsx @@ -92,3 +92,12 @@ export const WithinGroup = () => ( ); + +export const WithArrow = () => ( + + + +); diff --git a/src/mantine-core/src/Tooltip/Tooltip.tsx b/src/mantine-core/src/Tooltip/Tooltip.tsx index 9bb0cd1e559..54da20e52dc 100644 --- a/src/mantine-core/src/Tooltip/Tooltip.tsx +++ b/src/mantine-core/src/Tooltip/Tooltip.tsx @@ -1,4 +1,4 @@ -import React, { cloneElement, forwardRef } from 'react'; +import React, { cloneElement, forwardRef, useRef } from 'react'; import { isElement, ForwardRefWithStaticComponents } from '@mantine/utils'; import { useMergedRef } from '@mantine/hooks'; import { getDefaultZIndex, useComponentDefaultProps } from '@mantine/styles'; @@ -67,6 +67,7 @@ const defaultProps: Partial = { }; const _Tooltip = forwardRef((props, ref) => { + const arrowRef = useRef(null); const { children, position, @@ -114,6 +115,7 @@ const _Tooltip = forwardRef((props, ref) => { onPositionChange, opened, events, + arrowRef, offset: offset + (withArrow ? arrowSize / 2 : 0), positionDependencies: [...positionDependencies, children], }); @@ -150,6 +152,9 @@ const _Tooltip = forwardRef((props, ref) => { {label} ; events: { hover: boolean; focus: boolean; touch: boolean }; positionDependencies: any[]; } @@ -47,11 +49,28 @@ export function useTooltip(settings: UseTooltip) { [setCurrentId, uid] ); - const { x, y, reference, floating, context, refs, update, placement } = useFloating({ + const { + x, + y, + reference, + floating, + context, + refs, + update, + placement, + middlewareData: { arrow: { x: arrowX, y: arrowY } = {} }, + } = useFloating({ placement: settings.position, open: opened, onOpenChange: onChange, - middleware: [offset(settings.offset), shift({ padding: 8 }), flip()], + middleware: [ + offset(settings.offset), + shift({ padding: 8 }), + flip(), + arrow({ + element: settings.arrowRef, + }), + ], }); const { getReferenceProps, getFloatingProps } = useInteractions([ @@ -82,6 +101,8 @@ export function useTooltip(settings: UseTooltip) { return { x, y, + arrowX, + arrowY, reference, floating, getFloatingProps, diff --git a/src/mantine-demos/src/demos/core/Popover/Popover.demo.arrow.tsx b/src/mantine-demos/src/demos/core/Popover/Popover.demo.arrow.tsx new file mode 100644 index 00000000000..9ee39ba97fd --- /dev/null +++ b/src/mantine-demos/src/demos/core/Popover/Popover.demo.arrow.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { Popover, Text, Button } from '@mantine/core'; + +const code = ` +import { Popover, Text, Button } from '@mantine/core'; + +function Demo() { + return ( + + + + + + This is to test popover arrow, the arrow will be the center postion. + + + ); +} +`; + +function Demo() { + return ( + + + + + + This is to test popover arrow, the arrow will be the center postion. + + + ); +} + +export const arrow: MantineDemo = { + type: 'demo', + code, + component: Demo, +}; diff --git a/src/mantine-demos/src/demos/core/Popover/index.ts b/src/mantine-demos/src/demos/core/Popover/index.ts index 21bf76154c3..30af08e1cb8 100644 --- a/src/mantine-demos/src/demos/core/Popover/index.ts +++ b/src/mantine-demos/src/demos/core/Popover/index.ts @@ -2,3 +2,4 @@ export { usage } from './Popover.demo.usage'; export { form } from './Popover.demo.form'; export { hover } from './Popover.demo.hover'; export { sameWidth } from './Popover.demo.sameWidth'; +export { arrow } from './Popover.demo.arrow'; diff --git a/src/mantine-demos/src/demos/core/Tooltip/Tooltip.demo.arrow.tsx b/src/mantine-demos/src/demos/core/Tooltip/Tooltip.demo.arrow.tsx index 14f6529d883..646b2448100 100644 --- a/src/mantine-demos/src/demos/core/Tooltip/Tooltip.demo.arrow.tsx +++ b/src/mantine-demos/src/demos/core/Tooltip/Tooltip.demo.arrow.tsx @@ -7,6 +7,14 @@ import { Tooltip, Button } from '@mantine/core'; function Demo() { return ( <> + + + + + + + + @@ -22,7 +30,20 @@ function Demo() { export function Demo() { return ( - + + + + + + + diff --git a/yarn.lock b/yarn.lock index 461025e38f6..1cfe9ba0661 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1866,34 +1866,32 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@floating-ui/core@^0.7.3": - version "0.7.3" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-0.7.3.tgz#d274116678ffae87f6b60e90f88cc4083eefab86" - integrity sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg== +"@floating-ui/core@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.0.1.tgz#00e64d74e911602c8533957af0cce5af6b2e93c8" + integrity sha512-bO37brCPfteXQfFY0DyNDGB3+IMe4j150KFQcgJ5aBP295p9nBGeHEs/p0czrRbtlHq4Px/yoPXO/+dOCcF4uA== -"@floating-ui/dom@^0.5.3": - version "0.5.4" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-0.5.4.tgz#4eae73f78bcd4bd553ae2ade30e6f1f9c73fe3f1" - integrity sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg== +"@floating-ui/dom@^1.0.0": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.0.2.tgz#c5184c52c6f50abd11052d71204f4be2d9245237" + integrity sha512-5X9WSvZ8/fjy3gDu8yx9HAA4KG1lazUN2P4/VnaXLxTO9Dz53HI1oYoh1OlhqFNlHgGDiwFX5WhFCc2ljbW3yA== dependencies: - "@floating-ui/core" "^0.7.3" + "@floating-ui/core" "^1.0.1" -"@floating-ui/react-dom-interactions@0.6.6": - version "0.6.6" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom-interactions/-/react-dom-interactions-0.6.6.tgz#8542e8c4bcbee2cd0d512de676c6a493e0a2d168" - integrity sha512-qnao6UPjSZNHnXrF+u4/n92qVroQkx0Umlhy3Avk1oIebm/5ee6yvDm4xbHob0OjY7ya8WmUnV3rQlPwX3Atwg== +"@floating-ui/react-dom-interactions@^0.10.1": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom-interactions/-/react-dom-interactions-0.10.1.tgz#45fc7c3d9a2ae58f0ef39078660e97594f484af8" + integrity sha512-mb9Sn/cnPjVlEucSZTSt4Iu7NAvqnXTvmzeE5EtfdRhVQO6L94dqqT+DPTmJmbiw4XqzoyGP+Q6J+I5iK2p6bw== dependencies: - "@floating-ui/react-dom" "^0.7.2" + "@floating-ui/react-dom" "^1.0.0" aria-hidden "^1.1.3" - use-isomorphic-layout-effect "^1.1.1" -"@floating-ui/react-dom@^0.7.2": - version "0.7.2" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-0.7.2.tgz#0bf4ceccb777a140fc535c87eb5d6241c8e89864" - integrity sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg== +"@floating-ui/react-dom@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.0.0.tgz#e0975966694433f1f0abffeee5d8e6bb69b7d16e" + integrity sha512-uiOalFKPG937UCLm42RxjESTWUVpbbatvlphQAU6bsv+ence6IoVG8JOUZcy8eW81NkU+Idiwvx10WFLmR4MIg== dependencies: - "@floating-ui/dom" "^0.5.3" - use-isomorphic-layout-effect "^1.1.1" + "@floating-ui/dom" "^1.0.0" "@humanwhocodes/config-array@^0.9.2": version "0.9.5"