diff --git a/apps/www/src/components/playground/tooltip-examples.tsx b/apps/www/src/components/playground/tooltip-examples.tsx index 7da5f7fea..807182877 100644 --- a/apps/www/src/components/playground/tooltip-examples.tsx +++ b/apps/www/src/components/playground/tooltip-examples.tsx @@ -7,29 +7,21 @@ export function TooltipExamples() { return ( - - + + }>Top + Top tooltip - - + + }>Right + Right tooltip - - + + }>Bottom + Bottom tooltip - - - - - - - - - - - - - - + + }>Left + Left tooltip diff --git a/apps/www/src/content/docs/components/tooltip/demo.ts b/apps/www/src/content/docs/components/tooltip/demo.ts index 2d20a5b19..bec9364e8 100644 --- a/apps/www/src/content/docs/components/tooltip/demo.ts +++ b/apps/www/src/content/docs/components/tooltip/demo.ts @@ -3,50 +3,52 @@ import { getPropsString } from '@/lib/utils'; export const getCode = (props: any) => { + const { children = 'Tooltip message', trackCursorAxis, ...rest } = props; return ` - - + + }> + Hover me + + + ${children} + `; }; export const playground = { type: 'playground', controls: { - message: { + children: { type: 'text', initialValue: 'Tooltip message' }, side: { type: 'select', - options: [ - 'top', - 'right', - 'bottom', - 'left', - 'top-left', - 'top-right', - 'bottom-left', - 'bottom-right' - ], + options: ['top', 'right', 'bottom', 'left'], defaultValue: 'top' }, - disabled: { - type: 'checkbox', - defaultValue: false + align: { + type: 'select', + options: ['start', 'center', 'end'], + defaultValue: 'center' }, - delayDuration: { + sideOffset: { type: 'number', - defaultValue: 200, - min: 0 + defaultValue: 4 }, - skipDelayDuration: { + alignOffset: { type: 'number', - defaultValue: 200, - min: 0 + defaultValue: 0 }, - followCursor: { + showArrow: { type: 'checkbox', + initialValue: false, defaultValue: false + }, + trackCursorAxis: { + type: 'select', + options: ['none', 'x', 'y', 'both'], + defaultValue: 'none' } }, getCode @@ -56,92 +58,108 @@ export const sideDemo = { type: 'code', code: ` - - - - - + + }>Top + Top tooltip - - + + }>Right + Right tooltip - - + + }>Bottom + Bottom tooltip - - + + }>Left + Left tooltip - - + ` +}; +export const alignDemo = { + type: 'code', + code: ` + + + }>Start + Start tooltip - - + + }>Center + Center tooltip - - + + }>End + End tooltip ` }; -export const followCursorDemo = { + +export const customDemo = { type: 'code', code: ` - - - - ` + + }>Hover me + +
+ Custom Tooltip +
+
+
` }; -export const customDemo = { + +export const providerDemo = { type: 'code', code: ` - - Custom Tooltip - -}> - -` + + + + }>Tooltip 1 + Top Left tooltip + + + }>Tooltip 2 + Top Right tooltip + + + ` }; -export const providerDemo = { +export const trackCursorDemo = { type: 'code', tabs: [ { - name: 'With Provider', + name: 'Both', code: ` - - - - - - - - - - - - - - - - ` + + }>Hover me + Tooltip follows cursor + ` }, { - name: 'Without Provider', + name: 'X', code: ` - - - - - - - - - - - - - - ` + + }>Hover me + Tooltip follows cursor + ` + }, + { + name: 'Y', + code: ` + + }>Hover me + Tooltip follows cursor + ` } ] }; + +export const arrowDemo = { + type: 'code', + code: ` + + }>Hover me + Tooltip with arrow + ` +}; diff --git a/apps/www/src/content/docs/components/tooltip/index.mdx b/apps/www/src/content/docs/components/tooltip/index.mdx index bd0485b06..d1a309373 100644 --- a/apps/www/src/content/docs/components/tooltip/index.mdx +++ b/apps/www/src/content/docs/components/tooltip/index.mdx @@ -4,7 +4,7 @@ description: A popup that displays information related to an element when it rec source: packages/raystack/components/tooltip --- -import { playground, sideDemo, customDemo,followCursorDemo, providerDemo } from "./demo.ts"; +import { playground, sideDemo, alignDemo, customDemo, providerDemo, trackCursorDemo, noArrowDemo, arrowDemo } from "./demo.ts"; @@ -20,9 +20,21 @@ The Tooltip component accepts various props to customize its behavior and appear +## Tooltip.Trigger Props + +The Trigger component wraps the element that activates the tooltip. + + + +## Tooltip.Content Props + +The Content component displays the tooltip content and controls positioning. + + + ## Tooltip.Provider Props -The TooltipProvider component serves as a context wrapper that provides global configuration and functionality to all tooltip instances within your application. +The Provider component serves as a context wrapper that provides global configuration and functionality to all tooltip instances within your application. @@ -30,10 +42,16 @@ The TooltipProvider component serves as a context wrapper that provides global c ### Side -The Tooltip component can be positioned in different directions using the `side` prop: +The Tooltip Content component can be positioned in different directions using the `side` prop: +### Align + +The Tooltip Content component can be aligned in different directions using the `align` prop: + + + ### Custom Content Tooltips can contain custom content using ReactNode: @@ -46,18 +64,14 @@ The TooltipProvider component can be used to provide a global configuration for -### Follow Cursor +### Track Cursor -When `followCursor` is true, the tooltip will follow the cursor and will be positioned relative to the cursor. +Use `trackCursorAxis` prop on the Root component to make the tooltip follow the cursor: - + -## Accessibility +### With Arrow -The Tooltip component follows WAI-ARIA guidelines for tooltips: +Show the arrow by setting `showArrow={true}` on the Content component: -- Uses `role="tooltip"` for proper semantic meaning -- Automatically manages focus and hover interactions -- Supports keyboard navigation -- Provides appropriate ARIA attributes for accessibility -- Manages enter/exit animations for smooth user experience + diff --git a/apps/www/src/content/docs/components/tooltip/props.ts b/apps/www/src/content/docs/components/tooltip/props.ts index 41c94f013..7f3111747 100644 --- a/apps/www/src/content/docs/components/tooltip/props.ts +++ b/apps/www/src/content/docs/components/tooltip/props.ts @@ -1,95 +1,101 @@ export interface TooltipProps { /** - * Content to display in the tooltip. + * The controlled open state of the tooltip. */ - message: string | React.ReactNode; + open?: boolean; /** - * Element that triggers the tooltip. + * The initial open state of the tooltip. */ - children: React.ReactNode; + defaultOpen?: boolean; /** - * Position of the tooltip relative to the trigger. - * @default "top" + * Event handler called when the open state of the tooltip changes. */ - side?: - | 'top' - | 'right' - | 'bottom' - | 'left' - | 'top-left' - | 'top-right' - | 'bottom-left' - | 'bottom-right'; + onOpenChange?: (open: boolean) => void; /** - * Whether the tooltip should follow the cursor. - * @default false + * Delay before showing the tooltip, in milliseconds. + * @default 200 */ - followCursor?: boolean; + delayDuration?: number; /** - * Whether the tooltip is disabled. - * @default false + * Prevents Tooltip from remaining open when hovering. Disabling this has accessibility consequences. */ - disabled?: boolean; + disableHoverableContent?: boolean; /** - * The controlled open state of the tooltip. + * Track cursor axis ('none', 'x', 'y', or 'both') + * @default 'none' */ - open?: boolean; + trackCursorAxis?: 'none' | 'x' | 'y' | 'both'; +} +export interface TooltipTriggerProps { /** - * The initial open state of the tooltip. + * React element to render as the trigger. Props will be merged onto this element. */ - defaultOpen?: boolean; + render?: React.ReactElement; /** - * Event handler called when the open state of the tooltip changes. + * Additional CSS class names + */ + className?: string; +} + +export interface TooltipContentProps { + /** + * Controls whether to show the arrow * @default false */ - onOpenChange?: (open: boolean) => void; + showArrow?: boolean; /** - * Delay before showing the tooltip, in milliseconds. - * Overrides the prop of TooltipProvider. - * @default 200 + * Side placement of the tooltip + * @default "top" */ - delayDuration?: number; + side?: 'top' | 'bottom' | 'left' | 'right'; /** - * Prevents Tooltip from remaining open when hovering. Disabling this has accessibility consequences. - * Overrides the prop of TooltipProvider. + * Alignment of the tooltip + * @default "center" */ - disableHoverableContent?: boolean; + align?: 'start' | 'center' | 'end'; + + /** + * Side offset for positioning + * @default 4 + */ + sideOffset?: number; /** - * Additional ID for Tooltip Content + * Align offset for positioning + * @default 0 */ - id?: string; + alignOffset?: number; /** - * Additional CSS class names. + * Additional CSS class names */ className?: string; } export interface TooltipProviderProps { /** - * Delay before showing the tooltip, in milliseconds. + * How long to wait before opening a tooltip. Specified in milliseconds. * @default 200 */ - delayDuration?: number; + delay?: number; /** - * Delay before showing the tooltip when moving between tooltips, in milliseconds. - * @default 200 + * How long to wait before closing a tooltip. Specified in milliseconds. */ - skipDelayDuration?: number; + closeDelay?: number; /** - * Prevents Tooltip from remaining open when hovering. Disabling this has accessibility consequences. + * Another tooltip will open instantly if the previous tooltip is closed within this timeout. Specified in milliseconds. + * @default 400 */ - disableHoverableContent?: boolean; + timeout?: number; } diff --git a/packages/raystack/components/sidebar/sidebar-root.tsx b/packages/raystack/components/sidebar/sidebar-root.tsx index 463d44a2a..b30915ebf 100644 --- a/packages/raystack/components/sidebar/sidebar-root.tsx +++ b/packages/raystack/components/sidebar/sidebar-root.tsx @@ -62,48 +62,48 @@ export const SidebarRoot = forwardRef( - - - + {tooltipMessage ?? + (open ? 'Click to collapse' : 'Click to expand')} + + + )} + {children} + ); } diff --git a/packages/raystack/components/tooltip/__tests__/tooltip.test.tsx b/packages/raystack/components/tooltip/__tests__/tooltip.test.tsx index 94a791e95..14d08141c 100644 --- a/packages/raystack/components/tooltip/__tests__/tooltip.test.tsx +++ b/packages/raystack/components/tooltip/__tests__/tooltip.test.tsx @@ -1,8 +1,7 @@ -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { Tooltip } from '../tooltip'; -import { TooltipProps } from '../tooltip-root'; const TRIGGER_TEXT = 'Hover me'; const MESSAGE_TEXT = 'Tooltip text'; @@ -11,13 +10,21 @@ const BasicTooltip = ({ message = MESSAGE_TEXT, children = TRIGGER_TEXT, ...props -}: Partial) => { +}: { + message?: string; + children?: React.ReactNode; + open?: boolean; + onOpenChange?: (open: boolean) => void; + delay?: number; +}) => { return ( - - + + {children}} /> + {message} ); }; + describe('Tooltip', () => { describe('Basic Rendering', () => { it('renders trigger content', () => { @@ -32,7 +39,7 @@ describe('Tooltip', () => { it('respects open prop', () => { render(); - expect(screen.queryAllByText(MESSAGE_TEXT)[0]).toBeInTheDocument(); + expect(screen.queryByText(MESSAGE_TEXT)).toBeInTheDocument(); }); it('shows tooltip on hover', async () => { @@ -42,8 +49,11 @@ describe('Tooltip', () => { const trigger = screen.getByText(TRIGGER_TEXT); await user.hover(trigger); - expect(screen.getAllByText(MESSAGE_TEXT)[0]).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(MESSAGE_TEXT)).toBeInTheDocument(); + }); }); + it('hides tooltip on mouse leave', async () => { const user = userEvent.setup(); render( @@ -67,7 +77,7 @@ describe('Tooltip', () => { const trigger = screen.getByText(TRIGGER_TEXT); await trigger.focus(); - expect(screen.getAllByText(MESSAGE_TEXT)[0]).toBeInTheDocument(); + expect(screen.getByText(MESSAGE_TEXT)).toBeInTheDocument(); }); it('hides tooltip on blur', async () => { @@ -75,9 +85,16 @@ describe('Tooltip', () => { const trigger = screen.getByText(TRIGGER_TEXT); await trigger.focus(); + + await waitFor(() => { + expect(screen.getByText(MESSAGE_TEXT)).toBeInTheDocument(); + }); + await trigger.blur(); - expect(screen.queryByText(MESSAGE_TEXT)).not.toBeInTheDocument(); + await waitFor(() => { + expect(screen.queryByText(MESSAGE_TEXT)).not.toBeInTheDocument(); + }); }); it('calls onOpenChange when state changes', async () => { @@ -87,7 +104,10 @@ describe('Tooltip', () => { const trigger = screen.getByText(TRIGGER_TEXT); await user.hover(trigger); - expect(onOpenChange).toHaveBeenCalled(); + + await waitFor(() => { + expect(onOpenChange).toHaveBeenCalled(); + }); }); }); @@ -95,8 +115,14 @@ describe('Tooltip', () => { it('works with explicit provider', () => { render( - Trigger 1 - Trigger 2 + + Trigger 1} /> + Tooltip 1 + + + Trigger 2} /> + Tooltip 2 + ); @@ -110,4 +136,32 @@ describe('Tooltip', () => { expect(screen.getByText(TRIGGER_TEXT)).toBeInTheDocument(); }); }); + + describe('Content', () => { + it('hides arrow when showArrow is false', () => { + render( + + Trigger} /> + Tooltip + + ); + + const tooltip = screen.getByText('Tooltip'); + const arrow = tooltip.parentElement?.querySelector('[class*="arrow"]'); + expect(arrow).not.toBeInTheDocument(); + }); + + it('shows arrow when showArrow is true', () => { + render( + + Trigger} /> + Tooltip + + ); + + const tooltip = screen.getByText('Tooltip'); + const arrow = tooltip.parentElement?.querySelector('[class*="arrow"]'); + expect(arrow).toBeInTheDocument(); + }); + }); }); diff --git a/packages/raystack/components/tooltip/tooltip-content.tsx b/packages/raystack/components/tooltip/tooltip-content.tsx new file mode 100644 index 000000000..411a59894 --- /dev/null +++ b/packages/raystack/components/tooltip/tooltip-content.tsx @@ -0,0 +1,76 @@ +'use client'; + +import { Tooltip as TooltipPrimitive } from '@base-ui/react'; +import { cx } from 'class-variance-authority'; +import { type ElementRef, forwardRef } from 'react'; +import { Text } from '../text'; +import styles from './tooltip.module.css'; + +export interface TooltipContentProps + extends Omit< + TooltipPrimitive.Positioner.Props, + 'className' | 'style' | 'render' + >, + TooltipPrimitive.Popup.Props { + /** + * Controls whether to show the arrow + * @default true + */ + showArrow?: boolean; +} + +export const TooltipContent = forwardRef< + ElementRef, + TooltipContentProps +>( + ( + { + className, + children, + showArrow = false, + style, + render, + ...positionerProps + }, + ref + ) => { + return ( + + + + {typeof children === 'string' ? {children} : children} + {showArrow && ( + + + + + + )} + + + + ); + } +); + +TooltipContent.displayName = 'Tooltip.Content'; diff --git a/packages/raystack/components/tooltip/tooltip-misc.tsx b/packages/raystack/components/tooltip/tooltip-misc.tsx new file mode 100644 index 000000000..e273e0f2b --- /dev/null +++ b/packages/raystack/components/tooltip/tooltip-misc.tsx @@ -0,0 +1,21 @@ +'use client'; + +import { Tooltip as TooltipPrimitive } from '@base-ui/react'; +import { forwardRef } from 'react'; + +export interface TooltipTriggerProps extends TooltipPrimitive.Trigger.Props {} + +export const TooltipTrigger = forwardRef< + HTMLButtonElement, + TooltipPrimitive.Trigger.Props +>((props, ref) => { + return ; +}); + +TooltipTrigger.displayName = 'Tooltip.Trigger'; + +export const TooltipProvider = (props: TooltipPrimitive.Provider.Props) => { + return ; +}; + +TooltipProvider.displayName = 'Tooltip.Provider'; diff --git a/packages/raystack/components/tooltip/tooltip-provider.tsx b/packages/raystack/components/tooltip/tooltip-provider.tsx deleted file mode 100644 index 7cb01ccd9..000000000 --- a/packages/raystack/components/tooltip/tooltip-provider.tsx +++ /dev/null @@ -1,36 +0,0 @@ -'use client'; - -import { Tooltip as TooltipPrimitive } from 'radix-ui'; -import { createContext, useContext } from 'react'; - -interface TooltipProviderContextValue - extends Omit {} - -const TooltipProviderContext = createContext< - TooltipProviderContextValue | undefined ->(undefined); - -export const useTooltipProvider = () => { - return useContext(TooltipProviderContext); -}; - -export interface TooltipProviderProps - extends TooltipPrimitive.TooltipProviderProps {} - -export const TooltipProvider = ({ - delayDuration = 200, - skipDelayDuration = 200, - ...props -}: TooltipProviderProps) => { - return ( - - - - ); -}; diff --git a/packages/raystack/components/tooltip/tooltip-root.tsx b/packages/raystack/components/tooltip/tooltip-root.tsx deleted file mode 100644 index 178ca1778..000000000 --- a/packages/raystack/components/tooltip/tooltip-root.tsx +++ /dev/null @@ -1,177 +0,0 @@ -'use client'; - -import { VariantProps, cva, cx } from 'class-variance-authority'; -import { Tooltip as TooltipPrimitive } from 'radix-ui'; -import { CSSProperties, ReactNode, useId, useMemo } from 'react'; -import { useMouse } from '~/hooks'; -import { Text } from '../text'; -import { TooltipProvider, useTooltipProvider } from './tooltip-provider'; -import styles from './tooltip.module.css'; -import { getTransformForPlacement } from './utils'; - -const tooltip = cva(styles.content, { - variants: { - side: { - top: styles['side-top'], - right: styles['side-right'], - bottom: styles['side-bottom'], - left: styles['side-left'], - 'top-left': styles['side-top-left'], - 'top-right': styles['side-top-right'], - 'bottom-left': styles['side-bottom-left'], - 'bottom-right': styles['side-bottom-right'] - } - }, - defaultVariants: { - side: 'top' - } -}); - -export interface TooltipProps - extends TooltipPrimitive.TooltipProps, - Omit, - VariantProps { - disabled?: boolean; - message: ReactNode; - classNames?: { - trigger?: string; - content?: string; - arrow?: string; - }; - triggerStyle?: CSSProperties; - contentStyle?: CSSProperties; - 'aria-label'?: string; - asChild?: boolean; - id?: string; - showArrow?: boolean; - followCursor?: boolean; -} -type TooltipSide = NonNullable; -type TooltipAlign = NonNullable; - -export const TooltipBase = ({ - children, - message, - disabled, - side = 'top', - classNames, - triggerStyle, - contentStyle, - 'aria-label': ariaLabel, - asChild = true, - showArrow = true, - id, - followCursor = false, - sideOffset = 4, - alignOffset = 0, - open, - defaultOpen, - delayDuration = 200, - onOpenChange, - disableHoverableContent, - ...props -}: TooltipProps) => { - const generatedId = useId(); - const tooltipId = id ?? generatedId; - const { - ref, - value: mouseValue, - reset - } = useMouse({ - resetOnExit: false, - enabled: followCursor - }); - - const computedSide = useMemo( - () => (side?.split('-')[0] || 'top') as TooltipSide, - [side] - ); - const computedAlign = useMemo( - () => - (side?.includes('-') - ? side.split('-')[1] === 'left' - ? 'start' - : 'end' - : 'center') as TooltipAlign, - [side] - ); - - if (disabled) return children; - - return ( - - -
- {children} -
-
- - - {typeof message === 'string' ? {message} : message} - {showArrow && ( - - )} - - -
- ); -}; - -export const TooltipRoot = (props: TooltipProps) => { - const provider = useTooltipProvider(); - - // If already inside a provider, just return the tooltip - if (provider) return ; - - // If not inside a provider, wrap with our own provider - return ( - - - - ); -}; - -TooltipRoot.displayName = 'TooltipRoot'; diff --git a/packages/raystack/components/tooltip/tooltip.module.css b/packages/raystack/components/tooltip/tooltip.module.css index 2794b9fcd..7029ab080 100644 --- a/packages/raystack/components/tooltip/tooltip.module.css +++ b/packages/raystack/components/tooltip/tooltip.module.css @@ -2,16 +2,18 @@ width: fit-content; display: flex; } +.positioner { + z-index: var(--rs-z-index-portal); +} .content { position: relative; box-sizing: border-box; - z-index: var(--rs-z-index-portal); padding: var(--rs-space-2) var(--rs-space-3); border-radius: var(--rs-radius-2); background: var(--rs-color-background-base-primary); border: 0.5px solid var(--rs-color-border-base-primary); - box-shadow: var(--rs-shadow-soft); + box-shadow: var(--rs-shadow-lifted); color: var(--rs-color-foreground-base-primary); font-size: var(--rs-font-size-mini); font-weight: var(--rs-font-weight-medium); @@ -20,64 +22,69 @@ width: fit-content; max-width: 400px; animation-duration: 400ms; + transform-origin: var(--transform-origin); } -.content[data-state="delayed-open"][data-side="top"]:not( - [data-follow-cursor="true"] - ) { +.content[data-open][data-side="top"]:not([data-instant]) { animation-name: slideDownAndFade; } -.content[data-state="delayed-open"][data-side="right"]:not( - [data-follow-cursor="true"] - ) { +.content[data-open][data-side="right"]:not([data-instant]) { animation-name: slideLeftAndFade; } -.content[data-state="delayed-open"][data-side="bottom"]:not( - [data-follow-cursor="true"] - ) { +.content[data-open][data-side="bottom"]:not([data-instant]) { animation-name: slideUpAndFade; } -.content[data-state="delayed-open"][data-side="left"]:not( - [data-follow-cursor="true"] - ) { +.content[data-open][data-side="left"]:not([data-instant]) { animation-name: slideRightAndFade; } +.content[data-side="top"][data-align="start"]:not([data-instant]) { + animation-name: slideDownRightAndFade; +} + +.content[data-side="top"][data-align="end"]:not([data-instant]) { + animation-name: slideDownLeftAndFade; +} + +.content[data-side="bottom"][data-align="start"]:not([data-instant]) { + animation-name: slideUpRightAndFade; +} +.content[data-side="bottom"][data-align="end"]:not([data-instant]) { + animation-name: slideUpLeftAndFade; +} + +.arrow svg { + color: var(--rs-color-background-base-primary); +} .arrow { - fill: var(--rs-color-background-base-primary); - width: 100%; - height: 100%; + z-index: var(--rs-z-index-portal); filter: drop-shadow(0 1px 0 var(--rs-color-border-base-primary)) drop-shadow(0 1px 1px var(--rs-color-border-base-primary)); - transform: translateX(-1px); - margin-top: -1px; } -/* Corner positions */ -.content[data-side="top"][data-align="start"]:not([data-follow-cursor="true"]) { - animation-name: slideDownRightAndFade; +/* Arrow positioning based on side */ +.arrow[data-side="top"] { + bottom: -7px; } -.content[data-side="top"][data-align="end"]:not([data-follow-cursor="true"]) { - animation-name: slideDownLeftAndFade; +.arrow[data-side="bottom"] { + transform: translateY(-100%) rotate(180deg); + top: 0; } -.content[data-side="bottom"][data-align="start"]:not( - [data-follow-cursor="true"] - ) { - animation-name: slideUpRightAndFade; +.arrow[data-side="left"], +.arrow[data-side="inline-start"] { + right: 0; + transform: translateY(-50%) translateX(100%) rotate(-90deg); } -.content[data-side="bottom"][data-align="end"]:not( - [data-follow-cursor="true"] - ) { - animation-name: slideUpLeftAndFade; -} -.content[data-follow-cursor="true"] { - animation-name: fade; +.arrow[data-side="right"], +.arrow[data-side="inline-end"] { + left: 0; + transform: translateY(-50%) translateX(-100%) rotate(90deg); } @keyframes fade { @@ -99,10 +106,6 @@ opacity: 0; transform: translateX(-2px); } - to { - opacity: 1; - transform: translateX(0); - } } @keyframes slideDownAndFade { @@ -110,10 +113,6 @@ opacity: 0; transform: translateY(-2px); } - to { - opacity: 1; - transform: translateY(0); - } } @keyframes slideLeftAndFade { @@ -121,10 +120,6 @@ opacity: 0; transform: translateX(2px); } - to { - opacity: 1; - transform: translateX(0); - } } @keyframes slideDownRightAndFade { @@ -132,10 +127,6 @@ opacity: 0; transform: translate(-2px, -2px); } - to { - opacity: 1; - transform: translate(0, 0); - } } @keyframes slideDownLeftAndFade { @@ -143,10 +134,6 @@ opacity: 0; transform: translate(2px, -2px); } - to { - opacity: 1; - transform: translate(0, 0); - } } @keyframes slideUpRightAndFade { @@ -154,10 +141,6 @@ opacity: 0; transform: translate(-2px, 2px); } - to { - opacity: 1; - transform: translate(0, 0); - } } @keyframes slideUpLeftAndFade { @@ -165,8 +148,4 @@ opacity: 0; transform: translate(2px, 2px); } - to { - opacity: 1; - transform: translate(0, 0); - } } diff --git a/packages/raystack/components/tooltip/tooltip.tsx b/packages/raystack/components/tooltip/tooltip.tsx index f0c80b2bc..a9638c80a 100644 --- a/packages/raystack/components/tooltip/tooltip.tsx +++ b/packages/raystack/components/tooltip/tooltip.tsx @@ -1,6 +1,9 @@ -import { TooltipProvider } from './tooltip-provider'; -import { TooltipRoot } from './tooltip-root'; +import { Tooltip as TooltipPrimitive } from '@base-ui/react'; +import { TooltipContent } from './tooltip-content'; +import { TooltipProvider, TooltipTrigger } from './tooltip-misc'; -export const Tooltip = Object.assign(TooltipRoot, { - Provider: TooltipProvider +export const Tooltip = Object.assign(TooltipPrimitive.Root, { + Provider: TooltipProvider, + Trigger: TooltipTrigger, + Content: TooltipContent }); diff --git a/packages/raystack/components/tooltip/utils.ts b/packages/raystack/components/tooltip/utils.ts deleted file mode 100644 index da42bad83..000000000 --- a/packages/raystack/components/tooltip/utils.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Tooltip as TooltipPrimitive } from 'radix-ui'; - -type MousePosition = { - x: number; - y: number; - width: number; - height: number; -}; - -export const getTransformForPlacement = ( - side: NonNullable, - align: NonNullable, - { x, y, width, height }: MousePosition -): string => { - const transforms = { - top: { - start: `translate(${x}px, ${y}px)`, - center: `translate(${x - width / 2}px, ${y}px)`, - end: `translate(${x - width}px, ${y}px)` - }, - bottom: { - start: `translate(${x}px, ${y - height}px)`, - center: `translate(${x - width / 2}px, ${y - height}px)`, - end: `translate(${x - width}px, ${y - height}px)` - }, - left: { - start: `translate(${x}px, ${y}px)`, - center: `translate(${x}px, ${y - height / 2}px)`, - end: `translate(${x}px, ${y - height}px)` - }, - right: { - start: `translate(${x - width}px, ${y}px)`, - center: `translate(${x - width}px, ${y - height / 2}px)`, - end: `translate(${x - width}px, ${y - height}px)` - } - }; - - return transforms[side][align]; -};