@@ -7,11 +7,7 @@ import {
77 PointerEvent ,
88 ReactNode ,
99 RefObject ,
10- useCallback ,
11- useEffect ,
1210 useMemo ,
13- useRef ,
14- useState ,
1511} from 'react' ;
1612import { OverlayProps } from 'react-aria' ;
1713import { useHotkeys } from 'react-hotkeys-hook' ;
@@ -60,12 +56,10 @@ import {
6056import { mergeProps } from '../../../utils/react' ;
6157import { ItemAction } from '../../actions/ItemAction' ;
6258import { ItemActionProvider } from '../../actions/ItemActionContext' ;
63- import {
64- CubeTooltipProviderProps ,
65- TooltipProvider ,
66- } from '../../overlays/Tooltip/TooltipProvider' ;
59+ import { CubeTooltipProviderProps } from '../../overlays/Tooltip/TooltipProvider' ;
6760import { HotKeys } from '../HotKeys' ;
6861import { ItemBadge } from '../ItemBadge' ;
62+ import { useAutoTooltip } from '../use-auto-tooltip' ;
6963
7064export interface CubeItemProps extends BaseProps , ContainerStyleProps {
7165 icon ?: ReactNode | 'checkbox' ;
@@ -454,207 +448,6 @@ const ItemElement = tasty({
454448 styleProps : CONTAINER_STYLES ,
455449} ) ;
456450
457- export function useAutoTooltip ( {
458- tooltip,
459- children,
460- labelProps,
461- isDynamicLabel = false , // if actions are set
462- } : {
463- tooltip : CubeItemProps [ 'tooltip' ] ;
464- children : ReactNode ;
465- labelProps ?: Props ;
466- isDynamicLabel ?: boolean ;
467- } ) {
468- // Determine if auto tooltip is enabled
469- // Auto tooltip only works when children is a string (overflow detection needs text)
470- const isAutoTooltipEnabled = useMemo ( ( ) => {
471- if ( typeof children !== 'string' ) return false ;
472-
473- // Boolean true enables auto overflow detection
474- if ( tooltip === true ) return true ;
475- if ( typeof tooltip === 'object' ) {
476- // If title is provided and auto is explicitly true, enable auto overflow detection
477- if ( tooltip . title ) {
478- return tooltip . auto === true ;
479- }
480-
481- // If no title is provided, default to auto=true unless explicitly disabled
482- const autoValue = tooltip . auto !== undefined ? tooltip . auto : true ;
483- return ! ! autoValue ;
484- }
485- return false ;
486- } , [ tooltip , children ] ) ;
487-
488- // Track label overflow for auto tooltip (only when enabled)
489- const externalLabelRef = ( labelProps as any ) ?. ref ;
490- const [ isLabelOverflowed , setIsLabelOverflowed ] = useState ( false ) ;
491- const elementRef = useRef < HTMLElement | null > ( null ) ;
492- const resizeObserverRef = useRef < ResizeObserver | null > ( null ) ;
493-
494- const checkLabelOverflow = useCallback ( ( ) => {
495- const label = elementRef . current ;
496- if ( ! label ) {
497- setIsLabelOverflowed ( false ) ;
498- return ;
499- }
500-
501- const hasOverflow = label . scrollWidth > label . clientWidth ;
502- setIsLabelOverflowed ( hasOverflow ) ;
503- } , [ ] ) ;
504-
505- useEffect ( ( ) => {
506- if ( isAutoTooltipEnabled ) {
507- checkLabelOverflow ( ) ;
508- }
509- } , [ isAutoTooltipEnabled , checkLabelOverflow ] ) ;
510-
511- // Attach ResizeObserver via callback ref to handle DOM node changes
512- const handleLabelElementRef = useCallback (
513- ( element : HTMLElement | null ) => {
514- // Call external callback ref to notify external refs
515- if ( externalLabelRef ) {
516- if ( typeof externalLabelRef === 'function' ) {
517- externalLabelRef ( element ) ;
518- } else {
519- ( externalLabelRef as any ) . current = element ;
520- }
521- }
522-
523- // Disconnect previous observer
524- if ( resizeObserverRef . current ) {
525- try {
526- resizeObserverRef . current . disconnect ( ) ;
527- } catch {
528- // do nothing
529- }
530- resizeObserverRef . current = null ;
531- }
532-
533- elementRef . current = element ;
534-
535- if ( element && isAutoTooltipEnabled ) {
536- // Create a fresh observer to capture the latest callback
537- const obs = new ResizeObserver ( ( ) => {
538- checkLabelOverflow ( ) ;
539- } ) ;
540- resizeObserverRef . current = obs ;
541- obs . observe ( element ) ;
542- // Initial check
543- checkLabelOverflow ( ) ;
544- } else {
545- setIsLabelOverflowed ( false ) ;
546- }
547- } ,
548- [ externalLabelRef , isAutoTooltipEnabled , checkLabelOverflow ] ,
549- ) ;
550-
551- // Cleanup on unmount
552- useEffect ( ( ) => {
553- return ( ) => {
554- if ( resizeObserverRef . current ) {
555- try {
556- resizeObserverRef . current . disconnect ( ) ;
557- } catch {
558- // do nothing
559- }
560- resizeObserverRef . current = null ;
561- }
562- elementRef . current = null ;
563- } ;
564- } , [ ] ) ;
565-
566- const finalLabelProps = useMemo ( ( ) => {
567- const props = {
568- ...( labelProps || { } ) ,
569- } ;
570-
571- delete props . ref ;
572-
573- return props ;
574- } , [ labelProps ] ) ;
575-
576- const renderWithTooltip = (
577- renderElement : (
578- tooltipTriggerProps ?: HTMLAttributes < HTMLElement > ,
579- tooltipRef ?: RefObject < HTMLElement > ,
580- ) => ReactNode ,
581- defaultTooltipPlacement : OverlayProps [ 'placement' ] ,
582- ) => {
583- // Handle tooltip rendering based on tooltip prop type
584- if ( tooltip ) {
585- // String tooltip - simple case
586- if ( typeof tooltip === 'string' ) {
587- return (
588- < TooltipProvider placement = { defaultTooltipPlacement } title = { tooltip } >
589- { ( triggerProps , ref ) => renderElement ( triggerProps , ref ) }
590- </ TooltipProvider >
591- ) ;
592- }
593-
594- // Boolean tooltip - auto tooltip on overflow
595- if ( tooltip === true ) {
596- if ( ( children || labelProps ) && ( isLabelOverflowed || isDynamicLabel ) ) {
597- return (
598- < TooltipProvider
599- placement = { defaultTooltipPlacement }
600- title = { children }
601- isDisabled = { ! isLabelOverflowed && isDynamicLabel }
602- >
603- { ( triggerProps , ref ) => renderElement ( triggerProps , ref ) }
604- </ TooltipProvider >
605- ) ;
606- }
607- }
608-
609- // Object tooltip - advanced configuration
610- if ( typeof tooltip === 'object' ) {
611- const { auto, ...tooltipProps } = tooltip ;
612-
613- // If title is provided and auto is not explicitly true, always show the tooltip
614- if ( tooltipProps . title && auto !== true ) {
615- return (
616- < TooltipProvider
617- placement = { defaultTooltipPlacement }
618- { ...tooltipProps }
619- >
620- { ( triggerProps , ref ) => renderElement ( triggerProps , ref ) }
621- </ TooltipProvider >
622- ) ;
623- }
624-
625- // If title is provided with auto=true, OR no title but auto behavior enabled
626- if ( ( children || labelProps ) && ( isLabelOverflowed || isDynamicLabel ) ) {
627- return (
628- < TooltipProvider
629- placement = { defaultTooltipPlacement }
630- title = { tooltipProps . title ?? children }
631- isDisabled = {
632- ! isLabelOverflowed &&
633- isDynamicLabel &&
634- tooltipProps . isDisabled !== true
635- }
636- { ...tooltipProps }
637- >
638- { ( triggerProps , ref ) => renderElement ( triggerProps , ref ) }
639- </ TooltipProvider >
640- ) ;
641- }
642- }
643- }
644-
645- return renderElement ( ) ;
646- } ;
647-
648- return {
649- labelRef : handleLabelElementRef ,
650- labelProps : finalLabelProps ,
651- isLabelOverflowed,
652- isAutoTooltipEnabled,
653- hasTooltip : ! ! tooltip ,
654- renderWithTooltip,
655- } ;
656- }
657-
658451const Item = < T extends HTMLElement = HTMLDivElement > (
659452 props : CubeItemProps ,
660453 ref : ForwardedRef < T > ,
0 commit comments