Skip to content

Instantly share code, notes, and snippets.

@laiso
Last active July 20, 2025 02:05
Show Gist options
  • Save laiso/70f7c4070f4c73bb358bf1d1f0b5e761 to your computer and use it in GitHub Desktop.
Save laiso/70f7c4070f4c73bb358bf1d1f0b5e761 to your computer and use it in GitHub Desktop.

Design Document

Overview

This design addresses the cursor inconsistency issue in the Settings menu, specifically after using the "Copy URL to clipboard" feature. The problem occurs when the confirmation message appears after copying, and hovering over this non-clickable element incorrectly displays a pointer cursor instead of the default cursor. This creates a misleading user experience as it suggests the element is clickable when it is not.

Architecture

The New Expensify application uses React Native for cross-platform development with React Native Web for the web implementation. Cursor styles in the web version are controlled through:

  1. Component-level styles
  2. Global CSS rules
  3. Platform-specific style implementations

The issue likely stems from one of these areas applying an incorrect cursor style to non-interactive elements or failing to reset the cursor style after an interaction.

Components and Interfaces

Affected Components

  1. Settings Menu Component: The parent component that renders the Settings options
  2. Share Code Component: The specific component that handles the "Copy URL to clipboard" functionality
  3. Toast/Notification Component: The component that displays the confirmation message after copying

Interface Changes

No user interface changes are required as this is a behavior fix rather than a visual redesign. The only change will be the cursor type displayed when hovering over non-clickable elements.

Data Models

No data model changes are required for this fix as it's purely a UI behavior issue.

Implementation Approach

Root Cause Analysis

Based on the issue description, there are several potential causes:

  1. Incorrect Style Inheritance: The non-clickable confirmation message might be inheriting cursor styles from a parent component.
  2. Missing Style Reset: After the copy action, the cursor style might not be properly reset.
  3. Overly Broad Style Rules: CSS rules might be applying pointer cursors too broadly.
  4. Event Handler Issues: Click handlers might remain attached even when they shouldn't be active.

Proposed Solution

The solution will involve:

  1. Identify the Component Hierarchy: Trace the component tree to identify which component is rendering the confirmation message.
  2. Analyze Style Definitions: Review the style definitions for the affected components.
  3. Fix Style Application: Ensure that:
    • Non-interactive elements explicitly use cursor: default in web environments
    • Interactive elements properly use cursor: pointer
    • Style resets occur appropriately after state changes

Implementation Details

The implementation will focus on the web platform since cursor styles are web-specific. The fix will likely involve:

  1. Component Style Updates: Ensuring the confirmation message component has the correct cursor style
  2. Style Specificity: Making sure the correct styles have sufficient specificity to override any inherited styles
  3. Testing on Multiple Platforms: Verifying the fix works on both Windows/Chrome and MacOS/Desktop

Error Handling

No specific error handling is required for this UI behavior fix.

Testing Strategy

Manual Testing

  1. Reproduction Test: Verify the issue can be reproduced following the steps in the bug report
  2. Fix Verification: After applying the fix, verify the cursor displays correctly
  3. Cross-Platform Testing: Test on both Windows/Chrome and MacOS/Desktop
  4. Regression Testing: Ensure other clickable and non-clickable elements in the Settings menu still display the correct cursor

Automated Testing

  1. Component Tests: Update or add tests for the affected components to verify cursor styles
  2. Visual Regression Tests: If available, use visual regression testing to verify the cursor behavior

Considerations

Performance Impact

The fix should have negligible performance impact as it only involves CSS style changes.

Accessibility

Proper cursor styling is an important accessibility feature as it provides visual feedback about interactive elements. This fix will improve accessibility by ensuring non-interactive elements don't appear to be interactive.

Cross-Platform Compatibility

The fix needs to work consistently across:

  • Windows/Chrome
  • MacOS/Desktop

Since this is a web-specific issue, mobile platforms (iOS/Android) are not affected.

diff --git a/src/components/BaseMiniContextMenuItem.tsx b/src/components/BaseMiniContextMenuItem.tsx
index fa0fdb45153..31057edf1c6 100644
--- a/src/components/BaseMiniContextMenuItem.tsx
+++ b/src/components/BaseMiniContextMenuItem.tsx
@@ -80,7 +80,6 @@ function BaseMiniContextMenuItem(
style={({hovered, pressed}) => [
styles.reportActionContextMenuMiniButton,
StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, isDelayButtonStateComplete), true),
- isDelayButtonStateComplete && styles.cursorDefault,
]}
>
{(pressableState) => (
diff --git a/src/components/ContextMenuItem.tsx b/src/components/ContextMenuItem.tsx
index fe7be45e1b5..ef8eea8e703 100644
--- a/src/components/ContextMenuItem.tsx
+++ b/src/components/ContextMenuItem.tsx
@@ -133,14 +133,18 @@ function ContextMenuItem(
title={itemText}
icon={itemIcon}
onPress={triggerPressAndUpdateSuccess}
- wrapperStyle={[styles.pr9, wrapperStyle]}
+ wrapperStyle={[
+ styles.pr9,
+ wrapperStyle,
+ // Add explicit cursor styling for the success state
+ !isThrottledButtonActive ? styles.cursorDefault : {}
+ ]}
success={!isThrottledButtonActive}
description={description}
descriptionTextStyle={styles.breakWord}
style={shouldLimitWidth && StyleUtils.getContextMenuItemStyles(windowWidth)}
isAnonymousAction={isAnonymousAction}
focused={isFocused}
- interactive={isThrottledButtonActive}
onFocus={onFocus}
onBlur={onBlur}
disabled={disabled}
diff --git a/src/components/Pressable/PressableWithDelayToggle.tsx b/src/components/Pressable/PressableWithDelayToggle.tsx
index 61781163752..491493aec10 100644
--- a/src/components/Pressable/PressableWithDelayToggle.tsx
+++ b/src/components/Pressable/PressableWithDelayToggle.tsx
@@ -116,7 +116,7 @@ function PressableWithDelayToggle(
tabIndex={-1}
accessible={false}
onPress={updatePressState}
- style={[styles.flexRow, pressableStyle, !isActive && styles.cursorDefault]}
+ style={[styles.flexRow, pressableStyle]}
>
{({hovered, pressed}) => (
<>
diff --git a/src/components/BaseMiniContextMenuItem.tsx b/src/components/BaseMiniContextMenuItem.tsx
index fa0fdb45153..31057edf1c6 100644
--- a/src/components/BaseMiniContextMenuItem.tsx
+++ b/src/components/BaseMiniContextMenuItem.tsx
@@ -80,7 +80,6 @@ function BaseMiniContextMenuItem(
style={({hovered, pressed}) => [
styles.reportActionContextMenuMiniButton,
StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, isDelayButtonStateComplete), true),
- isDelayButtonStateComplete && styles.cursorDefault,
]}
>
{(pressableState) => (
diff --git a/src/components/ContextMenuItem.tsx b/src/components/ContextMenuItem.tsx
index fe7be45e1b5..9f4eb0da53c 100644
--- a/src/components/ContextMenuItem.tsx
+++ b/src/components/ContextMenuItem.tsx
@@ -140,10 +140,10 @@ function ContextMenuItem(
style={shouldLimitWidth && StyleUtils.getContextMenuItemStyles(windowWidth)}
isAnonymousAction={isAnonymousAction}
focused={isFocused}
- interactive={isThrottledButtonActive}
onFocus={onFocus}
onBlur={onBlur}
- disabled={disabled}
+ disabled={disabled || !isThrottledButtonActive} // コピー成功時は無効化して、ポインターカーソルを表示しないようにする
+ shouldUseDefaultCursorWhenDisabled={!isThrottledButtonActive} // 成功状態の場合はデフォルトカーソルを使用
shouldShowLoadingSpinnerIcon={shouldShowLoadingSpinnerIcon}
/>
);
diff --git a/src/components/Pressable/PressableWithDelayToggle.tsx b/src/components/Pressable/PressableWithDelayToggle.tsx
index 61781163752..491493aec10 100644
--- a/src/components/Pressable/PressableWithDelayToggle.tsx
+++ b/src/components/Pressable/PressableWithDelayToggle.tsx
@@ -116,7 +116,7 @@ function PressableWithDelayToggle(
tabIndex={-1}
accessible={false}
onPress={updatePressState}
- style={[styles.flexRow, pressableStyle, !isActive && styles.cursorDefault]}
+ style={[styles.flexRow, pressableStyle]}
>
{({hovered, pressed}) => (
<>

New Expensify Product Overview

New Expensify is a reimagination of payments built on a foundation of chat. It's designed to simplify expense management, reimbursements, and financial interactions between people and businesses.

Core Features

  • Chat-Based Expense Management: Submit expenses, request money, and manage financial interactions through a chat interface
  • Multi-Platform Support: Available on iOS, Android, Web, and Desktop
  • Workspaces: Organize expenses and teams by workspace/policy
  • Expense Tracking: Submit receipts, track mileage, and manage expenses
  • Reimbursements: Request and receive money directly through the app
  • Company Cards: Manage and track company card expenses
  • Wallet: Connect bank accounts for direct reimbursements
  • Offline Support: App designed to work offline-first with data syncing when connection is restored

Target Users

  • Individuals looking to split expenses and request money
  • Small to large businesses managing employee expenses
  • Teams needing to track and manage shared expenses
  • Finance departments handling reimbursements and expense policies

The app focuses on creating a seamless, chat-based experience for all financial interactions while maintaining robust expense tracking and management capabilities.

Requirements Document

Introduction

This feature addresses a UI inconsistency in the Settings menu where the cursor displays as a pointer (hand) when hovering over non-clickable elements after copying a URL to the clipboard. Specifically, after clicking "Copy URL to clipboard" in Settings => Share code, the confirmation message appears, but hovering over this message incorrectly shows a pointer cursor instead of the default cursor. This creates a misleading user experience as it suggests the element is clickable when it is not.

Requirements

Requirement 1

User Story: As a user, I want consistent cursor behavior throughout the application so that I can clearly identify which elements are interactive and which are not.

Acceptance Criteria

  1. WHEN a user hovers over non-clickable elements in the Settings menu THEN the system SHALL display the default cursor.
  2. WHEN a user clicks "Copy URL to clipboard" in Settings => Share code THEN the system SHALL display a confirmation message with the default cursor on hover.
  3. WHEN a user hovers over clickable elements in the Settings menu THEN the system SHALL display the pointer cursor.
  4. WHEN the cursor style changes after copying to clipboard THEN the system SHALL maintain consistent cursor behavior across all supported platforms (Windows/Chrome, MacOS/Desktop).

Requirement 2

User Story: As a developer, I want to identify and fix the root cause of incorrect cursor styling to prevent similar issues in other parts of the application.

Acceptance Criteria

  1. WHEN analyzing the code THEN the system SHALL identify the component or CSS rule causing the incorrect cursor behavior.
  2. WHEN implementing the fix THEN the system SHALL ensure the solution doesn't negatively impact other UI elements.
  3. WHEN the fix is applied THEN the system SHALL maintain proper cursor behavior even after multiple interactions with the "Copy URL to clipboard" feature.

New Expensify Project Structure

Directory Organization

Root Level

  • android/: Android-specific native code and configuration
  • ios/: iOS-specific native code and configuration
  • src/: Main application source code
  • assets/: Static assets (images, fonts, animations)
  • desktop/: Electron desktop app configuration
  • web/: Web-specific files and configuration
  • tests/: Test files (unit, e2e, performance)
  • docs/: Documentation and help site
  • patches/: Custom patches for npm packages
  • scripts/: Build and utility scripts
  • config/: Configuration files for build tools
  • .kiro/: Steering files for AI assistance

Source Code (src/)

  • components/: Reusable React components
  • libs/: Library functions and utilities
  • pages/: Screen components organized by feature
  • styles/: Styling constants and theme definitions
  • hooks/: Custom React hooks
  • types/: TypeScript type definitions
  • utils/: Utility functions
  • languages/: Internationalization files
  • setup/: App initialization code

Key Files

  • src/App.tsx: Main application component
  • src/Expensify.tsx: Root component with providers
  • src/CONST.ts: Application constants
  • src/CONFIG.ts: Environment configuration
  • src/ROUTES.ts: Navigation route definitions
  • src/ONYXKEYS.ts: Keys for Onyx state management
  • index.js: Entry point for the application

Naming Conventions

  1. Files: Named after the component/function/constants they export

    • Components: PascalCase (e.g., Text.tsx)
    • Functions: camelCase (e.g., guid.ts)
    • Constants: UPPERCASE (e.g., CONST.ts)
    • Utility files: PascalCase (e.g., DateUtils.ts)
  2. Platform-Specific Extensions:

    • Mobile: .native.js
    • iOS/Android: .ios.js/.android.js
    • Web: .website.js
    • Desktop: .desktop.js

Architecture Patterns

Data Flow

  1. Onyx: Central state management with persistent storage

    • Components subscribe to Onyx keys using withOnyx()
    • Non-React code subscribes using Onyx.connect()
  2. Actions: Business logic layer

    • Handle API calls and data manipulation
    • Update Onyx state
    • Located in src/libs/actions/
  3. UI Components:

    • Reflect data from Onyx
    • Pass user input to Actions
    • Never directly modify Onyx state

Component Structure

  • Presentational Components: UI-only components in src/components/
  • Container Components: Connect to Onyx state, usually in src/pages/
  • HOCs: Higher-Order Components like withOnyx use camelCase naming

Testing Organization

  • unit/: Unit tests for individual components and functions
  • e2e/: End-to-end tests for user flows
  • perf-test/: Performance regression tests
  • utils/: Test utilities and helpers

Implementation Plan

  • 1. Investigate the cursor behavior issue in Settings menu

    • Reproduce the issue by following the steps in the bug report
    • Identify the component hierarchy involved in the "Copy URL to clipboard" feature
    • Analyze the current cursor styles applied to elements in the Settings menu
    • Requirements: 2.1
  • 2. Analyze the component structure and styling

    • 2.1 Locate the Share code component in the codebase

      • Identify the component that handles the "Copy URL to clipboard" functionality
      • Examine how the confirmation message is rendered after copying
      • Requirements: 2.1
    • 2.2 Inspect the cursor styles in the component

      • Review the CSS/style definitions that affect cursor appearance
      • Identify which style rules are causing the incorrect pointer cursor
      • Determine if the issue is in component-specific styles or global styles
      • Requirements: 2.1
  • 3. Implement the cursor style fix

    • 3.1 Create a targeted fix for the confirmation message component

      • Add or modify style definitions to ensure non-clickable elements use the default cursor
      • Ensure the fix is specific to the affected component to avoid side effects
      • Requirements: 1.1, 1.2, 2.2
    • 3.2 Update any global styles if necessary

      • If the issue is caused by overly broad style rules, refine them to be more specific
      • Ensure interactive elements still correctly display the pointer cursor
      • Requirements: 1.1, 1.3, 2.2
  • 4. Create tests for the fix

    • 4.1 Write unit tests for cursor styles

      • Create or update tests that verify the correct cursor styles are applied
      • Test both interactive and non-interactive elements
      • Requirements: 2.3
    • 4.2 Create a manual test plan

      • Document steps to verify the fix works as expected
      • Include verification on both Windows/Chrome and MacOS/Desktop
      • Requirements: 1.4, 2.3
  • 5. Verify the fix across platforms

    • Test the fix on Windows/Chrome
    • Test the fix on MacOS/Desktop
    • Ensure the cursor behavior is consistent across platforms
    • Requirements: 1.4, 2.3
  • 6. Refactor and clean up

    • Review the implementation for any code improvements
    • Ensure the fix follows project coding standards
    • Add comments explaining the fix for future reference
    • Requirements: 2.2

New Expensify Technical Stack

Core Technologies

  • Framework: React Native (cross-platform for iOS, Android, Web, and Desktop)
  • Language: TypeScript/JavaScript
  • State Management: Onyx (custom Pub/Sub library with persistent storage)
  • Navigation: React Navigation
  • Desktop: Electron
  • Web: React Native Web
  • Styling: Custom styling system with platform-specific implementations

Key Libraries & Dependencies

  • API Communication: Expensify-Common
  • Authentication: React Native Apple/Google Authentication
  • UI Components: Custom components with some third-party libraries
  • Media: Expo modules for camera, image manipulation, and audio
  • Maps: Mapbox
  • PDF Handling: React Native PDF
  • Internationalization: FormatJS libraries
  • Analytics: Firebase, FullStory

Build System

  • Package Manager: npm (v10.7.0)
  • Node.js: v20.15.1
  • iOS Build: Xcode, CocoaPods, Fastlane
  • Android Build: Gradle, Fastlane
  • Web Build: Webpack
  • Desktop Build: Electron Builder
  • Testing: Jest, React Testing Library, Reassure (performance testing)
  • Linting: ESLint, TypeScript
  • CI/CD: GitHub Actions

Common Commands

Setup & Installation

# Install dependencies
npm install

# Setup HTTPS for development
npm run setup-https

# Install iOS dependencies
npm run pod-install

# Configure Mapbox SDK (required for maps functionality)
npm run configure-mapbox

Development

# Run web app
npm run web

# Run iOS app
npm run ios

# Run Android app
npm run android

# Run desktop app
npm run desktop

# Run tests
npm test

# Type checking
npm run typecheck

# Lint code
npm run lint

Building

# Build web app (production)
npm run build

# Build desktop app (production)
npm run desktop-build

# Build iOS app
npm run ios-build

# Build Android app
npm run android-build

Architecture Principles

  1. Offline First: All data is stored locally and synced when online
  2. Data Flow: Server → Disk → UI → User → UI → Server
  3. Onyx: Central state management with persistent storage
  4. Actions: Business logic layer between UI and data
  5. UI Layer: Reflects data from persistent storage via Onyx bindings
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment