Skip to content

[Due for payment 2025-04-23] [$500] Update Datepicker UX #52621

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
dannymcclain opened this issue Nov 15, 2024 · 122 comments
Closed

[Due for payment 2025-04-23] [$500] Update Datepicker UX #52621

dannymcclain opened this issue Nov 15, 2024 · 122 comments
Assignees
Labels
Awaiting Payment Auto-added when associated PR is deployed to production Daily KSv2 External Added to denote the issue can be worked on by a contributor Improvement Item broken or needs improvement. NewFeature Something to build that is a new item.

Comments

@dannymcclain
Copy link
Contributor

dannymcclain commented Nov 15, 2024

Problem

Currently our datepicker inputs always show the calendar popover, and this leads to a pretty cumbersome interface in places like the search filters where we have both a before and after date input.

CleanShot 2024-11-15 at 08 54 52@2x

Solution

Coming from this thread, let’s update our datepicker so that the calendar popover only appears when the input is focused.

CleanShot.2024-11-15.at.08.49.24.mp4

image

Notes:

  • For this update, we’re going to leave the placeholder text as is. Focusing the input should only change the border to green and make the popover appear. (It should not bring up the keyboard on mobile.)
  • Selecting a date in the popover should set the date in the input and blur the input (thereby dismissing the calendar popover).
  • We also need to update the selected state of the date to match our designs (green background, bold text, with the same text color we use for our green buttons).
  • For the date filter in search and other places where there may be multiple inputs, we do not want to auto-focus the first date input (as this would hide the input below it), but in situations where there is only one date input (like clearing your status for example) we do want to autofocus the input to reveal the popover right away.
  • We plan on doing a “phase 2” update to the datepicker where we will allow users to select a date by either using the popover OR by typing a date into the input, likely using some kind of guided input (example), so let’s keep that in mind as we’re making this update. But to be clear, for phase 1, we’re not planning on tackling the guided input aspect.

Figma file

cc @Expensify/design

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~021857439292509917630
  • Upwork Job ID: 1857439292509917630
  • Last Price Increase: 2025-01-02
  • Automatic offers:
    • shubham1206agra | Contributor | 105277860
@dannymcclain dannymcclain added Weekly KSv2 Improvement Item broken or needs improvement. labels Nov 15, 2024
@dannymcclain dannymcclain self-assigned this Nov 15, 2024
@dannymcclain dannymcclain added the External Added to denote the issue can be worked on by a contributor label Nov 15, 2024
@melvin-bot melvin-bot bot changed the title Update Datepicker UX [$250] Update Datepicker UX Nov 15, 2024
Copy link

melvin-bot bot commented Nov 15, 2024

Job added to Upwork: https://www.upwork.com/jobs/~021857439292509917630

@melvin-bot melvin-bot bot added the Help Wanted Apply this label when an issue is open to proposals by contributors label Nov 15, 2024
Copy link

melvin-bot bot commented Nov 15, 2024

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

@melvin-bot melvin-bot bot added Daily KSv2 and removed Weekly KSv2 labels Nov 15, 2024
@dannymcclain
Copy link
Contributor Author

@JmillsExpensify any opinion on which project makes the most sense for this? I was thinking #retain but I will defer to the smart people!

@Pholenk
Copy link

Pholenk commented Nov 15, 2024

Hi @dannymcclain I can't see the image and video attached. Can you reupload it, please?

@neonbhai
Copy link
Contributor

Proposal

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

Update Datepicker UX

What is the root cause of that problem?

Feature Request

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

We will:

  1. Wrap the Date Popover in a Modal

    • In DatePicker.tsx, wrap the date popover here with a <Modal> tag:
      <View
      style={[styles.datePickerPopover, styles.border]}
      collapsable={false}
      >
      <CalendarPicker
      minDate={minDate}
      maxDate={maxDate}
      value={selectedDate}
      onSelected={onSelected}
      />
      </View>
    • The modal will have no backdrop and will appear only when the TextInput is focused.
  2. Handle Focus State

    • Since we need the onFocus & onBlur prop added by InputWrapper, we will use the datePicker from a component that wraps it in InputWrapper
    • Introduce an isDatePopoverVisible state to toggle the popover visibility based on onFocus and onBlur events.
         onFocus={() => setIsDatePopoverVisible(true)}
         onBlur={() => setIsDatePopoverVisible(false)}
  3. Autofocus Behavior

    • For date filters (e.g., in search), we will avoid auto-focusing the first date input to prevent hiding subsequent inputs using a prop shouldAutofocus
    • shouldAutofocus will betrue by default

@dannymcclain
Copy link
Contributor Author

@Pholenk Just re-uploaded! Hopefully that worked. Those are pretty important to be able to see 😂

@daledah
Copy link
Contributor

daledah commented Nov 15, 2024

Edited by proposal-police: This proposal was edited at 2024-11-16 08:52:21 UTC.

Proposal

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

What is the root cause of that problem?

  • New feature

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

  1. Base code change in DatePicker:
function DatePicker(
    {
        containerStyles,
        defaultValue,
        disabled,
        errorText,
        inputID,
        label,
        maxDate = setYear(new Date(), CONST.CALENDAR_PICKER.MAX_YEAR),
        minDate = setYear(new Date(), CONST.CALENDAR_PICKER.MIN_YEAR),
        onInputChange,
        onTouched,
        placeholder,
        value,
        shouldSaveDraft = false,
        formID,
    }: DatePickerProps,
    ref: ForwardedRef<BaseTextInputRef>,
) {
    const styles = useThemeStyles();
    const {translate} = useLocalize();
    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
    const [selectedDate, setSelectedDate] = useState(value || defaultValue || undefined);
    const {shouldUseNarrowLayout} = useResponsiveLayout();

    useEffect(() => {
        // Value is provided to input via props and onChange never fires. We have to save draft manually.
        if (shouldSaveDraft && !!formID) {
            FormActions.setDraftValues(formID, {[inputID]: selectedDate});
        }

        if (selectedDate === value || !value) {
            return;
        }

        setSelectedDate(value);
    }, [formID, inputID, selectedDate, shouldSaveDraft, value]);

+    const show = useRef<() => void>();
+    const onSelected = (newValue: string) => {
+        onTouched?.();
+        onInputChange?.(newValue);
+        setSelectedDate(newValue);
+        hideTooltipRef.current?.();
+        ref?.current?.blur();
+    };
+    const hideTooltipRef = useRef<() => void>();

+    const [containerInputWidth, setContainerInputWidth] = useState();

+    const renderTooltipContent = useCallback(() => {
+        return (
+            <View
+                style={[styles.datePickerPopover, styles.border, {width: containerInputWidth, pointerEvents: 'auto', zIndex: +10000}]}
+                collapsable={false}
+            >
+                <CalendarPicker
+                    minDate={minDate}
+                    maxDate={maxDate}
+                    value={selectedDate}
+                    onSelected={onSelected}
+                />
+            </View>
+        );
+    }, [containerInputWidth, minDate, maxDate, selectedDate, onSelected]);

    return (
+        <View style={styles.datePickerRoot}>
+            <GenericTooltip
+                shouldUseOverlay
+                wrapperStyle={{backgroundColor: 'transparent'}}
+                renderTooltipContent={renderTooltipContent}
+                // eslint-disable-next-line react/jsx-props-no-spreading
+                anchorAlignment={{horizontal: 'center', vertical: 'top'}}
+            >
+                {({showTooltip, hideTooltip, updateTargetBounds}) => {
+                    hideTooltipRef.current = hideTooltip;
+                    return (
+                        <View
+                            onLayout={(e: LayoutChangeEventWithTarget) => {
+                                // e.target is specific to native, use e.nativeEvent.target on web instead
+                                const target = e.target || e.nativeEvent.target;
+                                show.current = () => measureTooltipCoordinate(target, updateTargetBounds, showTooltip);
+                            }}
+                            style={[shouldUseNarrowLayout ? styles.flex2 : {}]}
+                        >
+                            <TextInput
+                                disableKeyboard
+                                onLayout={(e) => {
+                                    setContainerInputWidth(e.nativeEvent.layout.width);
+                                }}
+                                ref={ref}
+                                inputID={inputID}
+                                forceActiveLabel
+                                icon={Expensicons.Calendar}
+                                label={label}
+                                accessibilityLabel={label}
+                                role={CONST.ROLE.PRESENTATION}
+                                value={selectedDate}
+                                placeholder={placeholder ?? translate('common.dateFormat')}
+                                errorText={errorText}
+                                containerStyles={containerStyles}
+                                disabled={disabled}
+                                onFocus={() => show?.current?.()}
+                                onBlur={() => hideTooltip()}
+                            />
+                        </View>
+                    );
+                }}
+            </GenericTooltip>
+        </View>
+    );
}

Explains: I am using GenericTooltip to render the date picker. it will wrap the text input component. The similar is used here:

  1. Then, in CalendarPicker, pass onMouseDown={(e) => e.preventDefault()} for each PressableWithFeedback such as:



    and
    <PressableWithoutFeedback

It will make sure the input is not blurred when we click on any button in the date picker.

  1. Update the selected state of the date to match our designs (green background, bold text, with the same text color we use for our green buttons). => This part is very easy, can be addressed in PR phase.

  2. Auto focus on input: Just need to useuseAutoFocusInput in where we want to focus. For example:

                    <InputWrapper ref={inputCallbackRef} /> 
    const {inputCallbackRef} = useAutoFocusInput()

What alternative solutions did you explore? (Optional)

Result

Screen.Recording.2024-11-27.at.17.28.40.mov

@Pholenk
Copy link

Pholenk commented Nov 16, 2024

Edited by proposal-police: This proposal was edited at 2024-11-21 19:23:22 UTC.

Proposal

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

Update Datepicker UX

What is the root cause of that problem?

UX enhancement

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

  • Add this line to track whether the input is focused, popover relative position, and container height
    const [isFocused, setIsFocused] = useState(false);
    const [popoverPosition, setPopoverPosition] = useState({
        top: 0,
        left: 0,
        bottom: 0,
        right: 0,
    });
    const [containerSize, setContainerSize] = useState({
        height: 50,
    });

to this line

  • Add this line to set reference locally so the functionality of the popover and the text input is not dependent to the ref props
const localRef = useRef<BaseTextInputRef>(null);
  • Add these lines to set popover show or hide dynamically
    const popoverDisplayStyle = useMemo(() => {
        if (isFocused) {
            return styles.datePickerPopoverShow;
        }

        return styles.datePickerPopoverHide;
    }, [isFocused, styles.datePickerPopoverHide, styles.datePickerPopoverShow]);
  • Change the styles in here
+        datePickerRoot: {
-             position: 'relative',
-             zIndex: 99,
+            width: '100%',
+        },
+        datePickerPopoverShow: {
+            display: 'flex',
+        },
+
+        datePickerPopoverHide: {
+            display: 'none',
+        },
  • To expose the reference of this component add these lines
useImperativeHandle(
        ref,
        () => {
            if (!localRef.current) {
                return {} as BaseTextInputRef;
            }
            return localRef.current;
        },
        [],
    );
  • Add these lines to simplify the changes of isFocused state
const toggleFocus = useCallback(() => {
        setIsFocused((prevState) => !prevState);
    }, []);
  • Change the component code
-        <View style={styles.datePickerRoot}>
-            <View style={[shouldUseNarrowLayout ? styles.flex2 : {}, styles.pointerEventsNone]}>
+        <View style={[styles.flex0, styles.datePickerRoot, containerSize]}>
+            <View style={[shouldUseNarrowLayout ? styles.flex2 : {}]}>
                <TextInput
-                    ref={ref}
+                   ref={localRef}
                    inputID={inputID}
                    forceActiveLabel
                    icon={Expensicons.Calendar}
                    label={label}
                    accessibilityLabel={label}
                    role={CONST.ROLE.PRESENTATION}
                    value={selectedDate}
                    placeholder={placeholder ?? translate('common.dateFormat')}
                    errorText={errorText}
                    containerStyles={containerStyles}
                    textInputContainerStyles={[styles.borderColorFocus]}
-                    inputStyle={[styles.pointerEventsNone]}
                    disabled={disabled}
-                    readOnly
+                    readOnly={false}
+                    disableKeyboard
+                    onFocus={toggleFocus}
+                    onBlur={toggleFocus}
+                    onLayout={onLayoutHandle}
                />
            </View>
            <View
-                style={[styles.datePickerPopover, styles.border]}
+                style={[styles.datePickerPopover, styles.border, popoverDisplayStyle, popoverPosition]}
                collapsable={false}
            >
                <CalendarPicker
                    minDate={minDate}
                    maxDate={maxDate}
                    value={selectedDate}
                    onSelected={onSelected}
                />
            </View>
        </View>

Result

Mobile

screencast-Genymotion-2024-11-21_23.54.13.555.mp4

Web

simplescreenrecorder-2024-11-22_02.14.18.mp4

What alternative solutions did you explore? (Optional)

Reminder: Please use plain English, be brief and avoid jargon. Feel free to use images, charts or pseudo-code if necessary. Do not post large multi-line diffs or write walls of text. Do not create PRs unless you have been hired for this job.

@daledah
Copy link
Contributor

daledah commented Nov 16, 2024

Proposal updated

Copy link

melvin-bot bot commented Nov 19, 2024

@dannymcclain, @thesahindia Whoops! This issue is 2 days overdue. Let's get this updated quick!

@melvin-bot melvin-bot bot added the Overdue label Nov 19, 2024
@dannymcclain
Copy link
Contributor Author

@thesahindia are you the official proposal reviewer for this one?

@thesahindia
Copy link
Member

@thesahindia are you the official proposal reviewer for this one?

Yeah, I am assigned as the C+.

@melvin-bot melvin-bot bot removed the Overdue label Nov 19, 2024
@thesahindia
Copy link
Member

Thanks for the proposals everyone! Could you guys please share the end results?

@Pholenk
Copy link

Pholenk commented Nov 21, 2024

I've updated my proposal to add the video of the change result.

cc: @thesahindia

Copy link

melvin-bot bot commented Nov 22, 2024

📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸

@melvin-bot melvin-bot bot added the Overdue label Nov 22, 2024
@dannymcclain
Copy link
Contributor Author

Not overdue. I think proposals are still being reviewed.

Copy link

melvin-bot bot commented Nov 25, 2024

@dannymcclain, @thesahindia Eep! 4 days overdue now. Issues have feelings too...

@thesahindia
Copy link
Member

Thanks for the proposals everyone! Could you guys please share the end results?

cc: @daledah @neonbhai

@melvin-bot melvin-bot bot changed the title [$500] Update Datepicker UX [Due for payment 2025-04-23] [$500] Update Datepicker UX Apr 16, 2025
@melvin-bot melvin-bot bot removed the Reviewing Has a PR in review label Apr 16, 2025
Copy link

melvin-bot bot commented Apr 16, 2025

Reviewing label has been removed, please complete the "BugZero Checklist".

Copy link

melvin-bot bot commented Apr 16, 2025

The solution for this issue has been 🚀 deployed to production 🚀 in version 9.1.28-15-staging and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:

If no regressions arise, payment will be issued on 2025-04-23. 🎊

For reference, here are some details about the assignees on this issue:

Copy link

melvin-bot bot commented Apr 16, 2025

BugZero Checklist: The PR adding this new feature has been merged! The following checklist (instructions) will need to be completed before the issue can be closed:

  • [@shubham1206agra] Please propose regression test steps to ensure the new feature will work correctly on production in further releases.
  • [@kadiealexander] Link the GH issue for creating/updating the regression test once above steps have been agreed upon.

@kadiealexander kadiealexander removed their assignment Apr 17, 2025
@kadiealexander kadiealexander added NewFeature Something to build that is a new item. and removed NewFeature Something to build that is a new item. labels Apr 17, 2025
Copy link

melvin-bot bot commented Apr 17, 2025

Triggered auto assignment to @trjExpensify (NewFeature), see https://stackoverflowteams.com/c/expensify/questions/14418#:~:text=BugZero%20process%20steps%20for%20feature%20requests for more details. Please add this Feature request to a GH project, as outlined in the SO.

@kadiealexander
Copy link
Contributor

Reassigning to handle payment as I'm heading OOO. Thanks for your help @trjExpensify!

@melvin-bot melvin-bot bot added Daily KSv2 and removed Weekly KSv2 labels Apr 23, 2025
@trjExpensify
Copy link
Contributor

👋 @shubham1206agra can we get the checklist here please, and confirm the number of regressions? Sounds like just the 1 here, as the other here was observed in the PR for a follow-up.

@shubham1206agra
Copy link
Contributor

👋 @shubham1206agra can we get the checklist here please, and confirm the number of regressions? Sounds like just the 1 here, as the other here was observed in the PR for a follow-up.

@trjExpensify The number of regression is 0 since this bug is repro on other inputs too #60243 (comment)

@trjExpensify
Copy link
Contributor

Hm, but the RCA places that original PR at fault (CC: @daledah @mollfpr) of introducing it. If that PR wasn't the cause, what was?

@shubham1206agra
Copy link
Contributor

This is a hidden bug that only appeared after the offending PR.

@trjExpensify Read this line

@shubham1206agra
Copy link
Contributor

The offending PR was marked because we only focused on DatePicker at that time.

@trjExpensify
Copy link
Contributor

Yeah, that line reads to me "this bug only appeared because of the offending PR".

@daledah
Copy link
Contributor

daledah commented Apr 23, 2025

I think that when implementing TextInput, there's no way to modify input values without focusing on the field, so we didn't put setTouchedInput inside onInputChange, which is the solution I proposed. So the culprit here could be #40757, as they created a way to "modify input values without focusing on the field" by adding clear button and overlooked this bug.

@trjExpensify
Copy link
Contributor

So the culprit here could be #40757, as they created a way to "modify input values without focusing on the field" by adding clear button and overlooked this bug.

Hm, but the X is only shown when the the field is focused here:

Image

Whereas with the DatePicker field it's displayed when not focused, which could be incorrect in itself? CC: @Expensify/design

Image

@daledah
Copy link
Contributor

daledah commented Apr 23, 2025

Hm, but the X is only shown when the the field is focused here:

With input fields that is focused by default, the X will be shown without user interactions.

modify input values without focusing on the field

I've misused the word "focusing" here, what I meant was "user press/click to focus on the field"

@trjExpensify
Copy link
Contributor

I've misused the word "focusing" here, what I meant was "user press/click to focus on the field"

Ah right, that makes way more sense. To rephrase:

So the culprit here could be #40757, as they created a way to modify input values when a field is focused by adding the clear button (X) and overlooked this bug.

@shubham1206agra
Copy link
Contributor

Since this is a new feature. So, only tests are require.d

Test Case 1: Manual Date Selection and Clear Functionality
Steps:

  1. Navigate to the Reports section.
  2. Tap the Filter button.
  3. Tap on the Date input field.
  4. On a wide screen (web), a popup should appear. On mobile, a modal should appear instead.
  5. Select a date from the DatePicker.
  6. Confirm the DatePicker closes automatically after a date is selected.
  7. Verify that a "Clear" (X) icon appears on the right side of the input once a date is chosen.
  8. Tap the Clear (X) icon – the input should be cleared.

Test Case 2: Auto-Opening Date Picker in Settings Flow

  1. Open the Settings screen.
  2. Tap the Status button.
  3. Tap the Clean After button.
  4. Select Custom.
  5. Tap the Date input field.
  6. The DatePicker should automatically open upon focusing the date input field (since it’s the only element on the screen).
  7. Tap Clear and verify the input field is cleared.

Since there's only one actionable field (the date input) on these screens, the DatePicker should open automatically. The date picker should be automatically opened on the next screen:

  • DateOfBirthPage – under Personal Details
  • SetDatePage – under Profile > Custom Status
  • IncorporationDateBusiness – under Reimbursement
  • IOURequestStepDate – under IOU Request flow
  • EditReportFieldDatePage – when editing a report field with date
  • DateOfBirthStep – under Reimbursement > Missing Personal Details

@trjExpensify
Copy link
Contributor

Test Case 2: Auto-Opening Date Picker in Settings Flow

  1. Open the Settings screen.
  2. Tap the Status button.
  3. Tap the Clean After button.
  4. Select Custom.
  5. Tap the Date input field.
  6. The DatePicker should automatically open upon focusing the date input field (since it’s the only element on the screen).
  7. Tap Clear and verify the input field is cleared.

Modifying this one. Typo in step 3, and step 5 is unclear you need to select the Date push row first to get to the date input screen.

Test Case 2: Auto-Opening Date Picker in Settings Flow

  1. Open the Settings screen.
  2. Tap the Status button.
  3. Tap the Clear After button.
  4. Select Custom.
  5. Click the Date push row
  6. Verify the Date input field is focused automatically (since it's the only element on the screen) and the DatePicker is revealed
  7. Tap Clear and verify the input field is cleared.

@trjExpensify
Copy link
Contributor

Payment summary as follows:

Offer sent.

@shubham1206agra
Copy link
Contributor

@trjExpensify Offer accepted

@trjExpensify
Copy link
Contributor

Paid, closing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting Payment Auto-added when associated PR is deployed to production Daily KSv2 External Added to denote the issue can be worked on by a contributor Improvement Item broken or needs improvement. NewFeature Something to build that is a new item.
Projects
Status: DONE
Development

No branches or pull requests