1- import React , { useRef } from "react" ;
1+ import React , { useRef , useState } from "react" ;
2+ import { createPortal } from "react-dom" ;
23import {
34 AUTO_COMPACTION_THRESHOLD_MIN ,
45 AUTO_COMPACTION_THRESHOLD_MAX ,
56} from "@/common/constants/ui" ;
6- import { TooltipWrapper , Tooltip } from "../Tooltip" ;
77
88// ----- Types -----
99
@@ -69,14 +69,54 @@ const applyThreshold = (pct: number, setThreshold: (v: number) => void): void =>
6969} ;
7070
7171/** Get tooltip text based on threshold */
72- const getTooltip = ( threshold : number , orientation : "horizontal" | "vertical" ) : string => {
72+ const getTooltipText = ( threshold : number , orientation : "horizontal" | "vertical" ) : string => {
7373 const isEnabled = threshold < DISABLE_THRESHOLD ;
7474 const direction = orientation === "horizontal" ? "left" : "up" ;
7575 return isEnabled
7676 ? `Auto-compact at ${ threshold } % · Drag to adjust (per-model)`
7777 : `Auto-compact disabled · Drag ${ direction } to enable (per-model)` ;
7878} ;
7979
80+ // ----- Portal Tooltip (vertical only) -----
81+
82+ interface VerticalSliderTooltipProps {
83+ text : string ;
84+ anchorRect : DOMRect ;
85+ threshold : number ;
86+ }
87+
88+ /**
89+ * Portal-based tooltip for vertical slider only.
90+ * Renders to document.body to escape the narrow container's clipping.
91+ * Horizontal slider uses native `title` attribute instead (simpler, no clipping issues).
92+ */
93+ const VerticalSliderTooltip : React . FC < VerticalSliderTooltipProps > = ( {
94+ text,
95+ anchorRect,
96+ threshold,
97+ } ) => {
98+ // Position to the left of the bar, aligned with threshold position
99+ const indicatorY = anchorRect . top + ( anchorRect . height * threshold ) / 100 ;
100+
101+ const style : React . CSSProperties = {
102+ position : "fixed" ,
103+ zIndex : 9999 ,
104+ background : "#2d2d30" ,
105+ color : "#cccccc" ,
106+ padding : "6px 10px" ,
107+ borderRadius : 4 ,
108+ fontSize : 12 ,
109+ whiteSpace : "nowrap" ,
110+ pointerEvents : "none" ,
111+ boxShadow : "0 2px 8px rgba(0,0,0,0.3)" ,
112+ right : window . innerWidth - anchorRect . left + 8 ,
113+ top : indicatorY ,
114+ transform : "translateY(-50%)" ,
115+ } ;
116+
117+ return createPortal ( < div style = { style } > { text } </ div > , document . body ) ;
118+ } ;
119+
80120// ----- Main component: ThresholdSlider -----
81121
82122/**
@@ -99,6 +139,7 @@ const getTooltip = (threshold: number, orientation: "horizontal" | "vertical"):
99139 */
100140export const ThresholdSlider : React . FC < ThresholdSliderProps > = ( { config, orientation } ) => {
101141 const containerRef = useRef < HTMLDivElement > ( null ) ;
142+ const [ isHovered , setIsHovered ] = useState ( false ) ;
102143 const isHorizontal = orientation === "horizontal" ;
103144
104145 const handleMouseDown = ( e : React . MouseEvent ) => {
@@ -131,7 +172,7 @@ export const ThresholdSlider: React.FC<ThresholdSliderProps> = ({ config, orient
131172
132173 const isEnabled = config . threshold < DISABLE_THRESHOLD ;
133174 const color = isEnabled ? "var(--color-plan-mode)" : "var(--color-muted)" ;
134- const title = getTooltip ( config . threshold , orientation ) ;
175+ const tooltipText = getTooltipText ( config . threshold , orientation ) ;
135176
136177 // Container styles
137178 const containerStyle : React . CSSProperties = {
@@ -170,38 +211,34 @@ export const ThresholdSlider: React.FC<ThresholdSliderProps> = ({ config, orient
170211 ? { width : 1 , height : 6 , background : color }
171212 : { width : 6 , height : 1 , background : color } ;
172213
173- // Indicator content (triangles + line)
174- const indicatorContent = (
175- < >
176- < Triangle direction = { isHorizontal ? "down" : "right" } color = { color } />
177- < div style = { lineStyle } />
178- < Triangle direction = { isHorizontal ? "up" : "left" } color = { color } />
179- </ >
180- ) ;
214+ // Get container rect for tooltip positioning (vertical only)
215+ const containerRect = containerRef . current ?. getBoundingClientRect ( ) ;
181216
182217 return (
183- < div ref = { containerRef } style = { containerStyle } onMouseDown = { handleMouseDown } >
218+ < div
219+ ref = { containerRef }
220+ style = { containerStyle }
221+ onMouseDown = { handleMouseDown }
222+ onMouseEnter = { ( ) => setIsHovered ( true ) }
223+ onMouseLeave = { ( ) => setIsHovered ( false ) }
224+ // Horizontal uses native title (simpler, no clipping issues with wide tooltips)
225+ title = { isHorizontal ? tooltipText : undefined }
226+ >
227+ { /* Visual indicator - pointer events disabled */ }
184228 < div style = { indicatorStyle } >
185- { isHorizontal ? (
186- // Horizontal: native title tooltip (works well positioned at cursor)
187- < div
188- title = { title }
189- style = { { display : "flex" , flexDirection : "column" , alignItems : "center" } }
190- >
191- { indicatorContent }
192- </ div >
193- ) : (
194- // Vertical: portal-based Tooltip to avoid clipping by overflow:hidden containers
195- < TooltipWrapper inline >
196- < div style = { { display : "flex" , flexDirection : "row" , alignItems : "center" } } >
197- { indicatorContent }
198- </ div >
199- < Tooltip position = "left" >
200- < div style = { { fontSize : 11 } } > { title } </ div >
201- </ Tooltip >
202- </ TooltipWrapper >
203- ) }
229+ < Triangle direction = { isHorizontal ? "down" : "right" } color = { color } />
230+ < div style = { lineStyle } />
231+ < Triangle direction = { isHorizontal ? "up" : "left" } color = { color } />
204232 </ div >
233+
234+ { /* Portal tooltip for vertical only - escapes narrow container clipping */ }
235+ { ! isHorizontal && isHovered && containerRect && (
236+ < VerticalSliderTooltip
237+ text = { tooltipText }
238+ anchorRect = { containerRect }
239+ threshold = { config . threshold }
240+ />
241+ ) }
205242 </ div >
206243 ) ;
207244} ;
0 commit comments