Skip to content
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

[WIP] Add info text to Upcoming Length control #3639

Merged
merged 14 commits into from
Oct 17, 2024
68 changes: 68 additions & 0 deletions packages/desktop-client/src/components/common/InfoBubble.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useState } from 'react';
import { useLocation } from 'react-router-dom';

import { SvgInformationCircle } from '../../icons/v2';
import { theme } from '../../style';

import { Text } from './Text';
import { View } from './View';

type InfoBubbleProps = {
label: string;
textWidth?: number;
};

export function InfoBubble({ label, textWidth }: InfoBubbleProps) {
const location = useLocation();
const [visible, setVisible] = useState(location.hash === '#info');

const width = textWidth || getStringWidth(label);

return visible ? (
<View style={{ userSelect: 'none' }}>
<SvgInformationCircle
style={{ height: '15px', cursor: 'pointer' }}
onClick={() => setVisible(false)}
onMouseLeave={() => setVisible(false)}
/>
<View
style={{
position: 'absolute',
left: '20px',
top: '-7px',
color: theme.buttonNormalText,
backgroundColor: theme.buttonNormalBackground,
padding: 5,
borderRadius: 4,
border: '1px solid ' + theme.buttonNormalBorder,
zIndex: 300,
boxShadow: theme.buttonPrimaryShadow,
}}
>
<Text
style={{
width,
}}
>
{label}
</Text>
</View>
</View>
) : (
<View style={{ userSelect: 'none' }}>
<SvgInformationCircle
style={{ height: '15px', cursor: 'pointer' }}
onClick={() => setVisible(true)}
onMouseOver={() => setTimeout(() => setVisible(true), 500)}
/>
</View>
);
Comment on lines +52 to +59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve hover behavior and event consistency.

The current implementation has a few potential issues:

  1. The setTimeout for the mouse over event can't be cancelled, potentially leading to unexpected behavior if the user moves the mouse away quickly.
  2. There's an inconsistency between the immediate response to clicks and the delayed response to hovers.

Consider the following improvements:

import React, { useState, useRef, useCallback } from 'react';

// ... other imports ...

export function InfoBubble({ label }: InfoBubbleProps) {
  const [visible, setVisible] = useState(false);
  const timeoutRef = useRef<NodeJS.Timeout | null>(null);

  const showBubble = useCallback(() => {
    if (timeoutRef.current) clearTimeout(timeoutRef.current);
    timeoutRef.current = setTimeout(() => setVisible(true), 500);
  }, []);

  const hideBubble = useCallback(() => {
    if (timeoutRef.current) clearTimeout(timeoutRef.current);
    setVisible(false);
  }, []);

  return (
    <View className={styles.container}>
      <SvgInformationCircle
        className={styles.icon}
        onClick={showBubble}
        onMouseEnter={showBubble}
        onMouseLeave={hideBubble}
      />
      {visible && (
        <View className={styles.bubble}>
          <Text>{label}</Text>
        </View>
      )}
    </View>
  );
}

This implementation:

  1. Uses a ref to store the timeout, allowing it to be cancelled.
  2. Applies the same delay to both click and hover events for consistency.
  3. Clears the timeout when the mouse leaves, preventing unexpected shows.
  4. Uses the useCallback hook to memoize the event handlers, optimizing performance.

These changes will make the component's behavior more predictable and consistent.

}

function getStringWidth(text: string): number {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d')!;
context.font = getComputedStyle(document.body).font;

return Math.ceil(context.measureText(text).width);
}
22 changes: 15 additions & 7 deletions packages/desktop-client/src/components/settings/Upcoming.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { type SyncedPrefs } from 'loot-core/types/prefs';
import { useSyncedPref } from '../../hooks/useSyncedPref';
import { type CSSProperties, theme } from '../../style';
import { Button } from '../common/Button2';
import { InfoBubble } from '../common/InfoBubble';
import { Select } from '../common/Select';
import { Text } from '../common/Text';
import { View } from '../common/View';
Expand Down Expand Up @@ -44,13 +45,20 @@ export function UpcomingLengthSettings() {
<Setting
primaryAction={
<View style={{ flexDirection: 'row', gap: '1em' }}>
<Column title="Upcoming Length">
<Select
options={options.map(x => [x.value || '7', x.label])}
value={upcomingLength}
onChange={newValue => setUpcomingLength(newValue)}
style={selectButtonStyle}
/>
<Column>
<View
style={{ flexDirection: 'row', alignItems: 'center', gap: 20 }}
>
<View title="Upcoming Length">
<Select
options={options.map(x => [x.value || '7', x.label])}
value={upcomingLength}
onChange={newValue => setUpcomingLength(newValue)}
style={selectButtonStyle}
/>
</View>
<InfoBubble label="Only the first instance of a recurring transaction will be shown." />
</View>
</Column>
</View>
}
Expand Down
6 changes: 6 additions & 0 deletions upcoming-release-notes/3639.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [ SamBobBarnes ]
---

Add info text to Upcoming Length control.