Skip to content

Commit cf6969a

Browse files
authored
Show the change my mind button in mobile when it fits (#3943)
* feat: update comment change my mind * feat: remove redundant check
1 parent cb84f38 commit cf6969a

File tree

8 files changed

+151
-60
lines changed

8 files changed

+151
-60
lines changed

front_end/messages/cs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1761,5 +1761,6 @@
17611761
"noPrivateNotes": "Žádné soukromé poznámky",
17621762
"privateNotes": "Soukromé poznámky",
17631763
"justNow": "právě teď",
1764+
"cmmButtonShort": "Mysl",
17641765
"othersCount": "Ostatní ({count})"
17651766
}

front_end/messages/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1755,5 +1755,6 @@
17551755
"noPrivateNotes": "No private notes yet",
17561756
"privateNotes": "Private Notes",
17571757
"justNow": "just now",
1758+
"cmmButtonShort": "Mind",
17581759
"none": "none"
17591760
}

front_end/messages/es.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1761,5 +1761,6 @@
17611761
"noPrivateNotes": "Aún no hay notas privadas",
17621762
"privateNotes": "Notas privadas",
17631763
"justNow": "justo ahora",
1764+
"cmmButtonShort": "Mente",
17641765
"othersCount": "Otros ({count})"
17651766
}

front_end/messages/pt.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,5 +1759,6 @@
17591759
"noPrivateNotes": "Ainda não há notas privadas",
17601760
"privateNotes": "Notas Privadas",
17611761
"justNow": "agora mesmo",
1762+
"cmmButtonShort": "Mente",
17621763
"othersCount": "Outros ({count})"
17631764
}

front_end/messages/zh-TW.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1758,5 +1758,6 @@
17581758
"noPrivateNotes": "尚無私人筆記",
17591759
"privateNotes": "私人筆記",
17601760
"justNow": "剛剛",
1761+
"cmmButtonShort": "心智",
17611762
"withdrawAfterPercentSetting2": "問題總生命周期後撤回"
17621763
}

front_end/messages/zh.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1763,5 +1763,6 @@
17631763
"noPrivateNotes": "尚無私人筆記",
17641764
"privateNotes": "私人筆記",
17651765
"justNow": "刚刚",
1766+
"cmmButtonShort": "心情",
17661767
"othersCount": "其他({count})"
17671768
}

front_end/src/components/comment_feed/comment.tsx

Lines changed: 20 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -241,15 +241,12 @@ const Comment: FC<CommentProps> = ({
241241
const userCanPredict = postData && canPredictQuestion(postData);
242242
const userForecast =
243243
postData?.question?.my_forecasts?.latest?.forecast_values[1] ?? 0.5;
244+
const isCommentAuthor = comment.author.id === user?.id;
244245
const isCmmButtonVisible =
245-
user?.id !== comment.author.id &&
246-
(!!postData?.question ||
247-
!!postData?.group_of_questions ||
248-
!!postData?.conditional);
249-
const isCmmButtonDisabled = !user || !userCanPredict;
250-
// TODO: find a better way to dedect whether on mobile or not. For now we need to know in JS
251-
// too and can't use tw classes
252-
const isMobileScreen = window.innerWidth < 640;
246+
!!postData?.question ||
247+
!!postData?.group_of_questions ||
248+
!!postData?.conditional;
249+
const isCmmButtonDisabled = !user || !userCanPredict || isCommentAuthor;
253250

254251
const {
255252
draftReady: editDraftReady,
@@ -372,8 +369,6 @@ const Comment: FC<CommentProps> = ({
372369
].includes(postData?.status ?? PostStatus.CLOSED);
373370

374371
const limitNotReached = factorsLimit > 0;
375-
const isCommentAuthor = comment.author.id === user?.id;
376-
377372
const canShowAddKeyFactorsButton =
378373
isCommentAuthor && questionNotClosed && canListKeyFactors;
379374

@@ -497,22 +492,6 @@ const Comment: FC<CommentProps> = ({
497492
}, [comment.id]);
498493

499494
const menuItems: MenuItemProps[] = [
500-
{
501-
hidden: !isMobileScreen || !isCmmButtonVisible,
502-
id: "cmm",
503-
element: (
504-
<div>
505-
<CmmToggleButton
506-
cmmContext={cmmContext}
507-
comment_id={comment.id}
508-
disabled={isCmmButtonDisabled}
509-
/>
510-
</div>
511-
),
512-
onClick: () => {
513-
return null; // handled by the button element
514-
},
515-
},
516495
{
517496
hidden: !(user?.id === comment.author.id),
518497
id: "edit",
@@ -839,7 +818,7 @@ const Comment: FC<CommentProps> = ({
839818

840819
<div className="mb-2 mt-1 h-7 overflow-visible">
841820
<div className="flex items-center justify-between text-sm leading-4 text-gray-900 dark:text-gray-900-dark">
842-
<div className="inline-flex items-center gap-2.5">
821+
<div className="flex min-w-0 flex-1 items-center gap-2.5">
843822
<CommentVoter
844823
voteData={{
845824
commentAuthorId: comment.author.id,
@@ -883,21 +862,15 @@ const Comment: FC<CommentProps> = ({
883862
icon={isKeyfactorsFormOpen ? faXmark : faPlus}
884863
className="size-4 p-1"
885864
/>
886-
{t("addKeyFactor")}
865+
<span className="hidden sm:inline">
866+
{t("addKeyFactor")}
867+
</span>
868+
<span className="sm:hidden">{t("add")}</span>
887869
</div>
888870
</>
889871
</Button>
890872
)}
891873

892-
{isCmmButtonVisible && !isMobileScreen && (
893-
<CmmToggleButton
894-
cmmContext={cmmContext}
895-
comment_id={comment.id}
896-
disabled={isCmmButtonDisabled}
897-
ref={cmmContext.setAnchorRef}
898-
/>
899-
)}
900-
901874
{!onProfile &&
902875
(isReplying ? (
903876
<Button
@@ -928,12 +901,18 @@ const Comment: FC<CommentProps> = ({
928901
{t("reply")}
929902
</Button>
930903
))}
904+
905+
{isCmmButtonVisible && (
906+
<CmmToggleButton
907+
cmmContext={cmmContext}
908+
comment_id={comment.id}
909+
disabled={isCmmButtonDisabled}
910+
ref={cmmContext.setAnchorRef}
911+
/>
912+
)}
931913
</div>
932914

933-
<div
934-
ref={isMobileScreen ? cmmContext.setAnchorRef : null}
935-
className={cn(treeDepth > 0 && "pr-1.5 md:pr-2")}
936-
>
915+
<div className={cn(treeDepth > 0 && "pr-1.5 md:pr-2")}>
937916
<DropdownMenu items={menuItems} />
938917
</div>
939918
</div>

front_end/src/components/comment_feed/comment_cmm.tsx

Lines changed: 125 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@ import {
1313
import {
1414
faChevronLeft,
1515
faChevronRight,
16-
faCaretUp,
1716
} from "@fortawesome/free-solid-svg-icons";
1817
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
1918
import { isNil } from "lodash";
2019
import { 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

2328
import { toggleCMMComment } from "@/app/(main)/questions/actions";
2429
import 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+
295342
interface 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+
384490
CmmToggleButton.displayName = "CmmToggleButton";
385491

386492
export { CmmToggleButton, CmmOverlay };

0 commit comments

Comments
 (0)