Skip to content

[Due for payment 2026-11-11] [$175] [CFI] Show the full report field name on hover when the name is cut off #105158

Open

Description

@melvin-bot

Problem

On a report, a long report field name gets cut off and you can't read it. Some workspaces use long text as a report field name, such as an authorization statement. You can't see the full name anywhere in the report view.

Solution

When a report field name is cut off, show the full name in a tooltip on hover. This matches how we handle other long labels in the App.

Expected result

  1. Create a workspace report field with a very long name.
  2. Open a report on that workspace.
  3. Hover over the cut-off field name. A tooltip shows the full name.

Notes

  • Wrapping the name instead of cutting it off is a possible follow-up. For now, we want the hover.

Reported in Slack.

Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @mallenexpensify

Activity

  1. changed the title [-]Report fields - Show the full field name on hover when the name is cut off[/-] [+][CFI] Report fields - Show the full field name on hover when the name is cut off[/+] on Oct 27, 2026
  2. trjExpensify commented on Oct 27, 2026

    @trjExpensify
    Contributor

    @MelvinBot proposal a solution for this.

  3. MelvinBot commented on Oct 27, 2026

    @MelvinBot
    Contributor

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    A long report field name is cut off on the report, and you can't see the full name anywhere.

    What is the root cause of that problem?

    Every report field on the report view renders through ReportFieldInlineInput. It passes the field name as the label of a TextInput (or DatePicker, which wraps TextInput). TextInputLabel forces that label onto one line with a tail ellipsis. It never adds a tooltip, so the clipped text is lost.

    The label also has pointerEventsNone, so it can't receive hover events itself. That keeps clicks going to the input, so we shouldn't remove it.

    What changes do you think we should make in order to solve the problem?

    Add an opt-in "tooltip when the label is cut off" option to the text input. Then turn it on for report fields.

    1. TextInputLabel (web): add an optional callback that reports whether the label is cut off. Measure it in onLayout the same way TextWithTooltip does (scrollWidth > offsetWidth). The native version ignores it, since there is no hover there.
    2. BaseTextInput: add a prop such as shouldShowLabelTooltipWhenTruncated. When it is set, wrap the input container (the element that already receives pointer events) in Tooltip. Pass text={isLabelTruncated ? label : undefined}. Always render the Tooltip and toggle only text. Toggling shouldRender changes the component tree, which would remount the input and drop focus or a draft value.
    3. DatePicker: forward the new prop to its TextInput. It lists its props explicitly, so this won't pass through on its own.
    4. ReportFieldInlineInput: pass the prop on all three branches: date, list, and text/formula/read-only.

    Because the label shrinks when the field is focused or filled, re-check truncation when the value or focus changes, not only on first layout.

    Code references

    What alternative solutions did you explore? (Optional)

    • Tooltip on the label only: this means removing pointerEventsNone. The label is absolutely positioned across the input, so it would then take clicks and text selection meant for the input.
    • Always show the tooltip: this is simpler, but the issue asks for it only when the name is cut off.
    • Wrap the name instead of truncating: the issue lists this as a possible follow-up.

    Mobile has no hover, so long names will still be cut off there. That matches the scope of this issue.


    Next Steps for Contributor+ team:
    To accept: @MelvinBot implement [this](https://github.com/Expensify/App/issues/105158) to create a draft PR.
    To refine: @MelvinBot <your feedback>
    To reject: Explain why you are rejecting Melvin's proposal.

  4. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Oct 27, 2026
  5. melvin-bot commented on Oct 27, 2026

    @melvin-bot
    Author

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @huult (External)

  6. changed the title [-][CFI] Report fields - Show the full field name on hover when the name is cut off[/-] [+][$175] [CFI] Report fields - Show the full field name on hover when the name is cut off[/+] on Oct 27, 2026
  7. melvin-bot commented on Oct 27, 2026

    @melvin-bot
    Author
  8. trjExpensify commented on Oct 27, 2026

    @trjExpensify
    Contributor

    @huult can you review Melvin's proposal and progress it if it's good please? Thanks!

  9. Enes2553 commented on Oct 27, 2026

    @Enes2553

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    When a workspace report field has a long name, its label in the report view (ReportFieldInlineInput — text, date, and list fields) is truncated with an ellipsis (...), and hovering over the field does not show a tooltip with the full field name.

    What is the root cause of that problem?

    1. In src/components/MoneyRequestReportView/ReportFieldInlineInput.tsx, all report field variants (DatePicker, FilterPopupButton's TextInput, and the default TextInput) pass label={label} down to BaseTextInput, which renders TextInputLabel.
    2. TextInputLabel renders <Text numberOfLines={!isMultiline || shouldLabelStayOnSingleLine ? 1 : undefined}> inside an <Animated.View style={[styles.textInputLabelContainer, animatedStyle, styles.pointerEventsNone]}>, which truncates long single-line labels with text-overflow: ellipsis, but neither TextInputLabel nor BaseTextInput detects truncation or renders a <Tooltip>.
    3. Important animation subtlety with TextInputLabel: In src/styles/index.ts, getTextInputLabelTransformation animates fontSize via a Reanimated worklet between 15px (INACTIVE_LABEL_SCALE = 1 when empty/unfocused) and 11px (ACTIVE_LABEL_SCALE = 0.7333 when focused or filled) over CONST.ANIMATED_TRANSITION (300ms).
      • On Web (react-native-web), a truncated <Text numberOfLines={1}> already occupies 100% of its container width (offsetWidth === clientWidth). When fontSize animates between 15px and 11px, scrollWidth changes while offsetWidth stays fixed, so onLayout (ResizeObserver) does not re-fire.
      • A label that truncates at 15px (empty/unfocused) may fit completely at 11px (filled/focused), so relying solely on onLayout leaves isLabelTruncated stale after focus/blur/value transitions.

    What changes do you think we should make in order to solve the problem?

    1. src/components/TextInput/TextInputLabel/types.ts:

      • Add an optional callback prop onLabelTruncatedChange?: (isTruncated: boolean) => void; to TextInputLabelProps.
    2. src/components/TextInput/TextInputLabel/index.tsx (and .native.tsx no-op compatibility):

      • Attach a ref (labelRef) to the <Text> element and a helper checkTruncation that checks el.scrollWidth > el.clientWidth on web (onLabelTruncatedChange?.(el.scrollWidth > el.clientWidth)).
      • Call checkTruncation inside <Text onLayout={checkTruncation}> (which handles initial mount and container width/window resize changes) and when label changes.
      • Use useAnimatedReaction on labelScale so that whenever labelScale settles at ACTIVE_LABEL_SCALE or INACTIVE_LABEL_SCALE after the font-size interpolation finishes, runOnJS(checkTruncation)() re-evaluates whether the label is truncated at its current font size:
        const labelRef = useRef<HTMLDivElement | Text | null>(null);
        
        const checkTruncation = useCallback(() => {
            if (!onLabelTruncatedChange || !labelRef.current || !('scrollWidth' in labelRef.current)) {
                return;
            }
            onLabelTruncatedChange(labelRef.current.scrollWidth > labelRef.current.clientWidth);
        }, [onLabelTruncatedChange]);
        
        useEffect(() => {
            checkTruncation();
        }, [label, checkTruncation]);
        
        useAnimatedReaction(
            () => labelScale.get(),
            (currentScale, previousScale) => {
                if (
                    currentScale !== previousScale &&
                    (currentScale === CONST.TEXT_INPUT_LABEL_SCALE.ACTIVE_LABEL_SCALE ||
                        currentScale === CONST.TEXT_INPUT_LABEL_SCALE.INACTIVE_LABEL_SCALE)
                ) {
                    runOnJS(checkTruncation)();
                }
            },
        );
      • Keep styles.pointerEventsNone on TextInputLabel's <Animated.View> so click-to-focus on TextInput, DatePicker, and FilterPopupButton continues to pass pointer events through to the underlying input/pressable without regression.
    3. src/components/TextInput/BaseTextInput/implementation/index.tsx:

      • Track const [isLabelTruncated, setIsLabelTruncated] = useState(false);.
      • Pass onLabelTruncatedChange={setIsLabelTruncated} to <TextInputLabel />.
      • Wrap the input's <PressableWithoutFeedback> (lines 380–617) in <Tooltip text={isLabelTruncated ? label : ''}>. Because BaseTooltip gates rendering on hasValidText (!StringUtils.isEmptyString(text)), the tooltip is completely inert whenever the label is not truncated, and automatically works across all report field types (Text, Formula, Date, List, and read-only/disabled fields).

    What alternative solutions did you explore? (Optional)

    • Removing styles.pointerEventsNone from TextInputLabel and wrapping <Text> inside TextInputLabel with TextWithTooltip: rejected because TextInputLabel is absolutely positioned (zIndex: 1, width: '100%') over the input box; enabling pointer events on it intercepts clicks/taps meant for focusing BaseTextInput or opening DatePicker / FilterPopupButton.
    • Checking scrollWidth > offsetWidth only inside onLayout: rejected because ResizeObserver does not fire when fontSize interpolates between INACTIVE_LABEL_SCALE (15px) and ACTIVE_LABEL_SCALE (11px) inside a fixed-width container.
  10. apoorv-xs commented on Oct 28, 2026

    @apoorv-xs
  11. melvin-bot commented on Oct 28, 2026

    @melvin-bot
    Author

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  12. huult commented on Oct 28, 2026

    @huult
    Contributor
    Screen.Recording.2026-10-28.at.10.31.39.mov

    We can proceed with @MelvinBot’s proposal.

    🎀 👀 🎀 c+ reviewed.

  13. 20 remaining items

  14. jeffrey-graham commented on Nov 7, 2026

    @jeffrey-graham
  15. melvin-bot commented on Nov 7, 2026

    @melvin-bot
    Author
  16. added and removed on Nov 9, 2026
  17. added
    Awaiting PaymentAuto-added when associated PR is deployed to production
    and removed on Nov 10, 2026
  18. melvin-bot commented on Nov 10, 2026

    @melvin-bot
    Author

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

  19. melvin-bot commented on Nov 10, 2026

    @melvin-bot
    Author

    Payment Summary

    Resolving PRs:

    BugZero Checklist (@mallenexpensify)

    • I have confirmed assignees, roles, and Upwork contracts look correct
    • I have paid out Upwork contracts / manual NewDot requests
    • [BugZero Assignee] I have created a GH issue for creating/updating the regression test once above steps have been agreed upon
  20. mallenexpensify commented on Nov 13, 2026

    @mallenexpensify
    Contributor

    Claude posting on behalf of Matt:

    Ready. Job price: $175 (standard, no Help Wanted, assigned 2026-10-27).

    • huult (C+): 100%, via NewDot

    No deploy blockers or regressions on the issue or the PR.

    Next: press Caps+Y, then pay.

    Evidence
    • C+ checklist, with test steps: comment
    • TestRail case: none yet
    • Resolving PR: #105279 by MelvinBot, merged 2026-11-03, approved by huult, puneetlath. No revert.
    • Test videos: C+ 5/5.

    PAYPREP v1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionDailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions