@@ -13,12 +13,17 @@ import {
1313import {
1414 faChevronLeft ,
1515 faChevronRight ,
16- faCaretUp ,
1716} from "@fortawesome/free-solid-svg-icons" ;
1817import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" ;
1918import { isNil } from "lodash" ;
2019import { useTranslations } from "next-intl" ;
21- import React , { useState , forwardRef , FC } from "react" ;
20+ import React , {
21+ useState ,
22+ forwardRef ,
23+ FC ,
24+ useLayoutEffect ,
25+ useCallback ,
26+ } from "react" ;
2227
2328import { toggleCMMComment } from "@/app/(main)/questions/actions" ;
2429import ForecastTextInput from "@/components/forecast_maker/forecast_text_input" ;
@@ -292,6 +297,48 @@ const CmmOverlay = ({
292297 ) ;
293298} ;
294299
300+ type LabelVariant = "full" | "mid" | "tiny" ;
301+
302+ function useFittingLabel <
303+ TContainer extends HTMLElement ,
304+ TFull extends HTMLElement ,
305+ TMid extends HTMLElement ,
306+ TTiny extends HTMLElement ,
307+ > ( params : {
308+ containerRef : React . RefObject < TContainer | null > ;
309+ fullRef : React . RefObject < TFull | null > ;
310+ midRef : React . RefObject < TMid | null > ;
311+ tinyRef : React . RefObject < TTiny | null > ;
312+ } ) {
313+ const [ variant , setVariant ] = useState < LabelVariant > ( "full" ) ;
314+
315+ const recompute = useCallback ( ( ) => {
316+ const container = params . containerRef . current ;
317+ if ( ! container ) return ;
318+
319+ const available = container . clientWidth ;
320+ const wFull = params . fullRef . current ?. offsetWidth ?? 0 ;
321+ const wMid = params . midRef . current ?. offsetWidth ?? 0 ;
322+
323+ const next =
324+ wFull <= available ? "full" : wMid <= available ? "mid" : "tiny" ;
325+
326+ setVariant ( ( prev ) => ( prev === next ? prev : next ) ) ;
327+ } , [ params . containerRef , params . fullRef , params . midRef ] ) ;
328+
329+ useLayoutEffect ( ( ) => {
330+ recompute ( ) ;
331+ const el = params . containerRef . current ;
332+ if ( ! el ) return ;
333+
334+ const ro = new ResizeObserver ( recompute ) ;
335+ ro . observe ( el ) ;
336+ return ( ) => ro . disconnect ( ) ;
337+ } , [ recompute , params . containerRef ] ) ;
338+
339+ return variant ;
340+ }
341+
295342interface CmmToggleButtonProps {
296343 comment_id : number ;
297344 disabled ?: boolean ;
@@ -320,34 +367,72 @@ const CmmToggleButton = forwardRef<HTMLButtonElement, CmmToggleButtonProps>(
320367 }
321368 } ;
322369
370+ const isDisabled = isLoading || ! ! disabled ;
371+
372+ const count = cmmContext . count ;
373+
374+ const fullLabel = `${ t ( "cmmButton" ) } (${ count } )` ;
375+ const midLabel = `${ t ( "cmmButtonShort" ) } (${ count } )` ;
376+ const tinyLabel = `(${ count } )` ;
377+
378+ const labelBoxRef = React . useRef < HTMLSpanElement > ( null ) ;
379+ const fullMeasureRef = React . useRef < HTMLSpanElement > ( null ) ;
380+ const midMeasureRef = React . useRef < HTMLSpanElement > ( null ) ;
381+ const tinyMeasureRef = React . useRef < HTMLSpanElement > ( null ) ;
382+
383+ const variant = useFittingLabel ( {
384+ containerRef : labelBoxRef ,
385+ fullRef : fullMeasureRef ,
386+ midRef : midMeasureRef ,
387+ tinyRef : tinyMeasureRef ,
388+ } ) ;
389+
323390 return (
324391 < Button
325392 size = "xxs"
326393 variant = "tertiary"
327394 onClick = { onChangedMyMind }
328- aria-label = "Changed my mind"
329- className = "whitespace-nowrap border border-blue-400 hover:bg-gray-100 dark:hover:bg-gray-100-dark"
330- disabled = { isLoading || disabled }
395+ aria-label = { t ( "cmmButton" ) }
396+ disabled = { isDisabled }
331397 ref = { ref }
332398 { ...cmmContext . getReferenceProps ( ) }
399+ className = { cn (
400+ "group relative inline-flex min-w-0 items-center gap-1 whitespace-nowrap rounded-sm border px-2 py-1 text-sm font-normal leading-[16px] tracking-tight transition-colors disabled:cursor-not-allowed" ,
401+ ! isDisabled &&
402+ ( cmmContext . cmmEnabled
403+ ? "hover:bg-olive-50 dark:hover:bg-olive-400/10"
404+ : "hover:bg-blue-50 dark:hover:bg-blue-600/20" ) ,
405+ isDisabled &&
406+ "border-gray-300 bg-gray-100 text-gray-400 hover:bg-gray-100 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-500"
407+ ) }
333408 >
334- < FontAwesomeIcon
335- icon = { faCaretUp }
409+ < DeltaBadge enabled = { cmmContext . cmmEnabled } disabled = { isDisabled } />
410+
411+ < span
412+ ref = { labelBoxRef }
336413 className = { cn (
337- "size-4 rounded-full" ,
338- {
339- "bg-gradient-to-b p-1 text-blue-700 group-hover:from-blue-400 group-hover:to-blue-100 dark:text-blue-700-dark dark:group-hover:from-blue-400-dark dark:group-hover:to-blue-100-dark" :
340- ! cmmContext . cmmEnabled ,
341- } ,
342- {
343- "bg-gradient-to-b from-olive-400 to-blue-100 p-1 text-olive-700 group-hover:from-olive-500 group-hover:to-blue-100 dark:from-olive-300-dark dark:to-blue-100-dark dark:text-olive-700-dark dark:group-hover:from-olive-500-dark dark:group-hover:to-blue-100-dark" :
344- cmmContext . cmmEnabled ,
345- }
414+ "min-w-0 flex-1 overflow-hidden whitespace-nowrap" ,
415+ "text-blue-700 dark:text-blue-700-dark" ,
416+ isDisabled && "text-current"
346417 ) }
347- />
418+ >
419+ { variant === "full"
420+ ? fullLabel
421+ : variant === "mid"
422+ ? midLabel
423+ : tinyLabel }
424+ </ span >
348425
349- < span className = "text-blue-700 dark:text-blue-700-dark" >
350- { t ( "cmmButton" ) } ({ cmmContext . count } )
426+ < span className = "pointer-events-none absolute -z-10 opacity-0" >
427+ < span ref = { fullMeasureRef } className = "whitespace-nowrap" >
428+ { fullLabel }
429+ </ span >
430+ < span ref = { midMeasureRef } className = "whitespace-nowrap" >
431+ { midLabel }
432+ </ span >
433+ < span ref = { tinyMeasureRef } className = "whitespace-nowrap" >
434+ { tinyLabel }
435+ </ span >
351436 </ span >
352437 </ Button >
353438 // <button
@@ -381,6 +466,27 @@ const CmmToggleButton = forwardRef<HTMLButtonElement, CmmToggleButtonProps>(
381466 }
382467) ;
383468
469+ const DeltaBadge : FC < { enabled : boolean ; disabled ?: boolean } > = ( {
470+ enabled,
471+ disabled,
472+ } ) => {
473+ return (
474+ < span
475+ aria-hidden = "true"
476+ className = { cn (
477+ "flex size-4 items-center justify-center rounded-full font-semibold leading-none" ,
478+ "text-[12px] transition-colors" ,
479+ disabled && "opacity-60" ,
480+ ! enabled && "bg-transparent text-blue-700 dark:text-blue-700-dark" ,
481+ enabled &&
482+ "bg-gradient-to-b from-olive-400 to-blue-100 p-1 text-olive-700 dark:from-olive-300-dark dark:to-blue-100-dark dark:text-olive-700-dark"
483+ ) }
484+ >
485+ ∆
486+ </ span >
487+ ) ;
488+ } ;
489+
384490CmmToggleButton . displayName = "CmmToggleButton" ;
385491
386492export { CmmToggleButton , CmmOverlay } ;
0 commit comments