Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions apps/docs/docs/components/overlay/Tooltip/_mobileExamples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,25 @@ function TooltipColorSchemeOptOut() {
);
}
```

### Visibility delay (press)

Use `openDelay` and `closeDelay` to slow down activation/dismissal when users tap through dense surfaces.

```jsx
function TooltipVisibilityDelay() {
return (
<HStack spacingHorizontal={2} gap={2} justifyContent="space-around">
<Tooltip content="Opens after 400ms" openDelay={400}>
<Button>Open delay 400ms</Button>
</Tooltip>
<Tooltip content="Closes after 150ms" closeDelay={150}>
<Button>Close delay 150ms</Button>
</Tooltip>
<Tooltip content="Open 400 / Close 150" openDelay={400} closeDelay={150}>
<Button>Open 400 / Close 150</Button>
</Tooltip>
</HStack>
);
}
```
22 changes: 22 additions & 0 deletions apps/docs/docs/components/overlay/Tooltip/_webExamples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,25 @@ You can use tooltips within `TextInput` to provide more context.
placeholder="Satoshi Nakamoto"
/>
```

### Visibility delay (hover)

Use `openDelay` and `closeDelay` to slow down hover activation and reduce accidental opens on dense UI. Keyboard focus still opens immediately.

```jsx live
function TooltipVisibilityDelay() {
return (
<HStack spacingHorizontal={2} gap={2} justifyContent="space-around">
<Tooltip content="Opens after 400ms" openDelay={400}>
<Button>Open delay 400ms</Button>
</Tooltip>
<Tooltip content="Closes after 150ms" closeDelay={150}>
<Button>Close delay 150ms</Button>
</Tooltip>
<Tooltip content="Open 400 / Close 150" openDelay={400} closeDelay={150}>
<Button>Open 400 / Close 150</Button>
</Tooltip>
</HStack>
);
}
```
4 changes: 4 additions & 0 deletions packages/common/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file.

<!-- template-start -->

## 8.29.0 ((12/11/2025, 02:43 PM PST))

This is an artificial version bump with no new change.

## 8.28.1 ((12/10/2025, 04:33 PM PST))

This is an artificial version bump with no new change.
Expand Down
2 changes: 1 addition & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coinbase/cds-common",
"version": "8.28.1",
"version": "8.29.0",
"description": "Coinbase Design System - Common",
"repository": {
"type": "git",
Expand Down
4 changes: 4 additions & 0 deletions packages/mcp-server/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file.

<!-- template-start -->

## 8.29.0 ((12/11/2025, 02:43 PM PST))

This is an artificial version bump with no new change.

## 8.28.1 ((12/10/2025, 04:33 PM PST))

This is an artificial version bump with no new change.
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coinbase/cds-mcp-server",
"version": "8.28.1",
"version": "8.29.0",
"description": "Coinbase Design System - MCP Server",
"repository": {
"type": "git",
Expand Down
6 changes: 6 additions & 0 deletions packages/mobile/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ All notable changes to this project will be documented in this file.

<!-- template-start -->

## 8.29.0 (12/11/2025 PST)

#### 🚀 Updates

- Add open/close visibility delays to Tooltip. [[#234](https://github.com/coinbase/cds/pull/234)] [DX-5010]

## 8.28.1 (12/10/2025 PST)

#### 🐞 Fixes
Expand Down
2 changes: 1 addition & 1 deletion packages/mobile/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coinbase/cds-mobile",
"version": "8.28.1",
"version": "8.29.0",
"description": "Coinbase Design System - Mobile",
"repository": {
"type": "git",
Expand Down
42 changes: 35 additions & 7 deletions packages/mobile/src/overlays/__stories__/TooltipV2.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,31 @@ const shortText = 'This is the short text.';
const longText =
'This is the really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really really long text.';

const DelayVariations = () => {
return (
<Example title="Delay Variations">
<VStack background="bgAlternate" gap={4} paddingY={2}>
<HStack justifyContent="space-evenly">
<Tooltip closeDelay={0} content="Opens after 400ms" openDelay={400}>
<Text font="label2">Open delay 400ms</Text>
</Tooltip>
<Tooltip closeDelay={200} content="Closes after 200ms" openDelay={0}>
<Text font="label2">Close delay 200ms</Text>
</Tooltip>
</HStack>
<HStack justifyContent="space-evenly">
<Tooltip closeDelay={150} content="Open 300 / Close 150" openDelay={300}>
<Text font="label2">Open 300 / Close 150</Text>
</Tooltip>
<Tooltip closeDelay={300} content="Open 500 / Close 300" openDelay={500}>
<Text font="label2">Open 500 / Close 300</Text>
</Tooltip>
</HStack>
</VStack>
</Example>
);
};

const ToolTipWithA11y = ({ tooltipText, yShiftByStatusBarHeight }: Omit<ContentTypes, 'title'>) => {
const triggerRef = useRef(null);
const { setA11yFocus } = useA11y();
Expand Down Expand Up @@ -206,7 +231,7 @@ const RNModalTest = () => {
);

return (
<>
<Example>
<Button onPress={setVisibleToOn}>Open RN Modal Test 2</Button>
<RNModal statusBarTranslucent={statusBarTranslucent} visible={visible}>
<VStack paddingTop={6} width="100%">
Expand All @@ -232,7 +257,7 @@ const RNModalTest = () => {
yShiftByStatusBarHeight={yShiftByStatusBarHeight}
/>
</RNModal>
</>
</Example>
);
};

Expand All @@ -249,11 +274,14 @@ const DisabledTest = () => {
const TooltipV2Screen = () => {
return (
<ExampleScreen>
<CDSModalTest />
<RNModalTest />
<Content title="Short Text Tooltip" tooltipText={shortText} />
<Content title="Long Text Tooltip" tooltipText={longText} />
<DisabledTest />
<VStack gap={2}>
<CDSModalTest />
<RNModalTest />
<DelayVariations />
<Content title="Short Text Tooltip" tooltipText={shortText} />
<Content title="Long Text Tooltip" tooltipText={longText} />
<DisabledTest />
</VStack>
</ExampleScreen>
);
};
Expand Down
66 changes: 57 additions & 9 deletions packages/mobile/src/overlays/tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Fragment, memo, useCallback, useMemo, useRef, useState } from 'react';
import React, { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Modal as RNModal, TouchableOpacity, View } from 'react-native';

import { InvertedThemeProvider } from '../../system/ThemeProvider';
Expand All @@ -24,24 +24,55 @@ export const Tooltip = memo(
visible,
invertColorScheme = true,
elevation,
openDelay = 0,
closeDelay = 0,
}: TooltipProps) => {
const subjectRef = useRef<View | null>(null);
const [isOpen, setIsOpen] = useState(false);
const isVisible = visible !== false && isOpen;
const [subjectLayout, setSubjectLayout] = useState<SubjectLayout>();
const openTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const closeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

const WrapperComponent = invertColorScheme ? InvertedThemeProvider : Fragment;

const { opacity, translateY, animateIn, animateOut } = useTooltipAnimation(placement);

const clearOpenTimeout = useCallback(() => {
if (openTimeoutRef.current) {
clearTimeout(openTimeoutRef.current);
openTimeoutRef.current = null;
}
}, []);

const clearCloseTimeout = useCallback(() => {
if (closeTimeoutRef.current) {
clearTimeout(closeTimeoutRef.current);
closeTimeoutRef.current = null;
}
}, []);

const handleRequestClose = useCallback(() => {
animateOut.start(() => {
setIsOpen(false);
onCloseTooltip?.();
});
}, [animateOut, onCloseTooltip]);
clearOpenTimeout();
clearCloseTimeout();

const closeTooltip = () => {
animateOut.start(() => {
setIsOpen(false);
onCloseTooltip?.();
});
};

if (closeDelay > 0) {
closeTimeoutRef.current = setTimeout(closeTooltip, closeDelay);
return;
}

closeTooltip();
}, [animateOut, clearCloseTimeout, clearOpenTimeout, closeDelay, onCloseTooltip]);

const handlePressSubject = useCallback(() => {
clearCloseTimeout();
subjectRef.current?.measure((x, y, width, height, pageOffsetX, pageOffsetY) => {
setSubjectLayout({
width,
Expand All @@ -50,9 +81,19 @@ export const Tooltip = memo(
pageOffsetY,
});
});
setIsOpen(true);
onOpenTooltip?.();
}, [onOpenTooltip]);
const openTooltip = () => {
setIsOpen(true);
onOpenTooltip?.();
};

clearOpenTimeout();
if (openDelay > 0) {
openTimeoutRef.current = setTimeout(openTooltip, openDelay);
return;
}

openTooltip();
}, [clearCloseTimeout, clearOpenTimeout, onOpenTooltip, openDelay]);

// The accessibility props for the trigger component. Trigger component
// equals the component where when you click on it, it will show the tooltip
Expand Down Expand Up @@ -86,6 +127,13 @@ export const Tooltip = memo(
[content, accessibilityLabelForContent, accessibilityHintForContent, handleRequestClose],
);

useEffect(() => {
return () => {
clearOpenTimeout();
clearCloseTimeout();
};
}, [clearCloseTimeout, clearOpenTimeout]);

return (
<View ref={subjectRef} collapsable={false}>
<TouchableOpacity
Expand Down
10 changes: 10 additions & 0 deletions packages/mobile/src/overlays/tooltip/TooltipProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ export type TooltipBaseProps = SharedProps &
* @default true
*/
visible?: boolean;
/**
* Delay (in ms) before showing the tooltip after press.
* @default 0
*/
openDelay?: number;
/**
* Delay (in ms) before hiding the tooltip after dismiss.
* @default 0
*/
closeDelay?: number;
/** Invert the theme's activeColorScheme for this component
* @default true
*/
Expand Down
29 changes: 28 additions & 1 deletion packages/mobile/src/overlays/tooltip/__tests__/Tooltip.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { renderHook } from '@testing-library/react-hooks';
import { fireEvent, render, screen } from '@testing-library/react-native';
import { act, fireEvent, render, screen } from '@testing-library/react-native';

import { Button } from '../../../buttons';
import { useDimensions } from '../../../hooks/useDimensions';
Expand Down Expand Up @@ -100,4 +100,31 @@ describe('Tooltip', () => {
expect(await screen.findByText(contentText)).toBeTruthy();
expect(onOpenTooltip).toHaveBeenCalled();
});

it('respects openDelay before showing tooltip content', async () => {
jest.useFakeTimers();
render(
<MockTooltip accessibilityHint="delay-hint" accessibilityLabel="delay-label" openDelay={300}>
<Button>{subjectText}</Button>
</MockTooltip>,
);

fireEvent.press(screen.getByAccessibilityHint('delay-hint'));

expect(screen.queryByText(contentText)).toBeNull();

act(() => {
jest.advanceTimersByTime(200);
});

expect(screen.queryByText(contentText)).toBeNull();

act(() => {
jest.advanceTimersByTime(100);
});

expect(await screen.findByText(contentText)).toBeTruthy();

jest.useRealTimers();
});
});
6 changes: 6 additions & 0 deletions packages/web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ All notable changes to this project will be documented in this file.

<!-- template-start -->

## 8.29.0 (12/11/2025 PST)

#### 🚀 Updates

- Add open/close visibility delays to Tooltip. [[#234](https://github.com/coinbase/cds/pull/234)] [DX-5010]

## 8.28.1 (12/10/2025 PST)

#### 🐞 Fixes
Expand Down
2 changes: 1 addition & 1 deletion packages/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@coinbase/cds-web",
"version": "8.28.1",
"version": "8.29.0",
"description": "Coinbase Design System - Web",
"repository": {
"type": "git",
Expand Down
Loading