Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5c1828f
feat(tooltip): align plain tooltip colors and typography with MD3
burczu Jun 10, 2026
d2e5c32
feat(tooltip): add fade enter/exit animation
burczu Jun 10, 2026
dd5586c
feat(tooltip): add rich tooltip variant
burczu Jun 10, 2026
83a82bc
docs(tooltip): showcase rich tooltip and document both variants
burczu Jun 10, 2026
76c4518
refactor(tooltip): extract shared useTooltipFade hook
burczu Jun 10, 2026
13f31ed
feat(fab): modernize FloatingActionButton to MD3
adrcotfas May 22, 2026
28ab4b1
feat(fab): add focus ring
adrcotfas May 27, 2026
880201c
fix: review findings
adrcotfas Jun 3, 2026
77f0833
refactor(tooltip): drop redundant token type annotations
burczu Jun 17, 2026
dd0633e
refactor(tooltip): render-prop API for Tooltip.Rich
burczu Jun 17, 2026
fa2dd79
fix(tooltip): label the rich tooltip dismiss backdrop
burczu Jun 17, 2026
72e7ca2
fix(tooltip): open Rich tooltip on web regardless of trigger
burczu Jun 19, 2026
982c83d
refactor(tooltip): derive mount during render, not in an effect
burczu Jun 23, 2026
5e1c36f
refactor(tooltip): measure the trigger in useLayoutEffect
burczu Jun 23, 2026
a151776
feat(tooltip): replace Reanimated shared values with CSS transitions
burczu Jun 24, 2026
d56bfef
refactor(tooltip): replace isWeb with inline Platform.OS checks
burczu Jun 24, 2026
fb8ec26
refactor(tooltip): replace timer arrays with single nullable refs
burczu Jun 24, 2026
3fe4981
refactor(tooltip): remove as type casts
burczu Jun 24, 2026
fefe3df
refactor(tooltip): replace cloneElement with render prop
burczu Jun 25, 2026
730f303
fix(tooltip): add collapsable={false} to trigger wrapper
burczu Jun 25, 2026
b652791
fix(tooltip): fix web hover flicker and cursor-leave dismiss
burczu Jun 29, 2026
75f0dd9
fix(tooltip): mobile interaction and Android animation bugs
burczu Jun 29, 2026
6d1dfac
fix(tooltip): fix Tooltip.Rich one-tap switching after scroll
burczu Jun 30, 2026
f85fcdb
fix(tooltip): cancel stale hide timer before scheduling a new one
burczu Jun 30, 2026
416f283
refactor(tooltip): replace as casts with proper ref type
burczu Jun 30, 2026
4e6a8a7
fix(tooltip): fix FAB tooltip and Tooltip.Rich touchscreen web
burczu Jun 30, 2026
0be9098
fix(fab): apply upstream a11y migration and fix RNTL v14 patterns
burczu Jul 1, 2026
b3e8d00
fix(tests): import Jest globals from @jest/globals in FABUtils test
burczu Jul 1, 2026
35ef55e
fix(fab): restore aria props and drop forwardRef wrapper
burczu Jul 2, 2026
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
2 changes: 1 addition & 1 deletion docs/src/components/BannerExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { BrowserOnly } from '@rspress/core/runtime';
import {
Avatar,
Button,
DarkTheme,
FAB,
DarkTheme,
LightTheme,
ProgressBar,
Provider,
Expand Down
13 changes: 10 additions & 3 deletions example/src/Examples/FABExample.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import * as React from 'react';
import { FlatList, ScrollView, StyleSheet, View } from 'react-native';
import type { NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
import {
FlatList,
NativeScrollEvent,
NativeSyntheticEvent,
ScrollView,
StyleSheet,
View,
} from 'react-native';

import {
Chip,
Divider,
FAB,
FABSize,
FABVariant,
List,
Switch,
Text,
useTheme,
} from 'react-native-paper';
import type { FABSize, FABVariant } from 'react-native-paper';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

type FabType = 'icon' | 'extended' | 'extendedTransforming' | 'menu';
Expand Down
132 changes: 98 additions & 34 deletions example/src/Examples/TooltipExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Appbar,
Avatar,
Banner,
Button,
Chip,
FAB,
IconButton,
Expand Down Expand Up @@ -46,17 +47,28 @@ const TooltipExample = () => {
header: () => (
<Appbar.Header elevated>
<Tooltip title="Go back">
<Appbar.BackAction onPress={() => navigation.goBack()} />
{(props) => (
<Appbar.BackAction
{...props}
onPress={() => navigation.goBack()}
/>
)}
</Tooltip>
<Appbar.Content title="Tooltips" />
<Tooltip title="Print ⌘ + P">
<Appbar.Action icon="printer" onPress={() => {}} />
{(props) => (
<Appbar.Action {...props} icon="printer" onPress={() => {}} />
)}
</Tooltip>
<Tooltip title="Search">
<Appbar.Action icon="magnify" onPress={() => {}} />
{(props) => (
<Appbar.Action {...props} icon="magnify" onPress={() => {}} />
)}
</Tooltip>
<Tooltip title="More options">
<Appbar.Action icon={MORE_ICON} onPress={() => {}} />
{(props) => (
<Appbar.Action {...props} icon={MORE_ICON} onPress={() => {}} />
)}
</Tooltip>
</Appbar.Header>
),
Expand All @@ -83,11 +95,14 @@ const TooltipExample = () => {
enterTouchDelay={transport.enterTouchDelay}
leaveTouchDelay={transport.leaveTouchDelay}
>
<IconButton
icon={transport.title.split(' ')[0].toLowerCase()}
size={24}
onPress={() => {}}
/>
{(props) => (
<IconButton
{...props}
icon={transport.title.split(' ')[0].toLowerCase()}
size={24}
onPress={() => {}}
/>
)}
</Tooltip>
))}
</View>
Expand All @@ -99,57 +114,106 @@ const TooltipExample = () => {
onValueChange={setTextAlign}
>
<Tooltip title="Align left">
<ToggleButton icon="format-align-left" value="left" />
{(props) => (
<ToggleButton
{...props}
icon="format-align-left"
value="left"
/>
)}
</Tooltip>
<Tooltip title="Align center">
<ToggleButton icon="format-align-center" value="center" />
{(props) => (
<ToggleButton
{...props}
icon="format-align-center"
value="center"
/>
)}
</Tooltip>
<Tooltip title="Align right">
<ToggleButton icon="format-align-right" value="right" disabled />
{(props) => (
<ToggleButton
{...props}
icon="format-align-right"
value="right"
disabled
/>
)}
</Tooltip>
</ToggleButton.Row>
</List.Section>
<List.Section title="Avatar">
<View style={styles.avatarContainer}>
<Tooltip title="Username">
<Avatar.Text label="U" />
{(props) => <Avatar.Text {...props} label="U" />}
</Tooltip>
</View>
</List.Section>
<List.Section title="Chip">
<View style={styles.chipContainer}>
<Tooltip title="Copied">
<Chip
mode="outlined"
avatar={
<Image
source={require('../../assets/images/avatar.png')}
accessibilityIgnoresInvertColors
/>
}
>
John Doe
</Chip>
{(props) => (
<Chip
{...props}
mode="outlined"
avatar={
<Image
source={require('../../assets/images/avatar.png')}
accessibilityIgnoresInvertColors
/>
}
>
John Doe
</Chip>
)}
</Tooltip>
</View>
</List.Section>
<List.Section title="Card">
<Tooltip title="Cafeteria, 1st floor">
<Card style={styles.cardContainer}>
<Card.Title
title="Lunch break"
subtitle="1:00-2:00 PM"
left={(props) => (
<Avatar.Icon {...props} icon="food-fork-drink" />
)}
/>
</Card>
{(props) => (
<Card {...props} style={styles.cardContainer}>
<Card.Title
title="Lunch break"
subtitle="1:00-2:00 PM"
left={(leftProps) => (
<Avatar.Icon {...leftProps} icon="food-fork-drink" />
)}
/>
</Card>
)}
</Tooltip>
</List.Section>
<List.Section title="Rich tooltips">
<View style={styles.iconButtonContainer}>
<Tooltip.Rich
title="Add to library"
content="Save this item to read it later from any of your devices."
actions={({ dismiss }) => (
<>
<Button compact onPress={dismiss}>
Learn more
</Button>
<Button compact mode="contained" onPress={dismiss}>
Add
</Button>
</>
)}
>
{(props) => <IconButton {...props} icon="plus" size={24} />}
</Tooltip.Rich>
<Tooltip.Rich content="A rich tooltip with body text only — no title or actions.">
{(props) => (
<IconButton {...props} icon="information" size={24} />
)}
</Tooltip.Rich>
</View>
</List.Section>
</ScreenWrapper>
<View style={styles.fabContainer}>
<Tooltip title="Press Me">
<FAB icon="plus" onPress={() => {}} />
{(props) => <FAB {...props} icon="plus" onPress={() => {}} />}
</Tooltip>
</View>
</>
Expand Down
12 changes: 9 additions & 3 deletions jest/testSetup.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@ jest.mock('react-native-worklets', () =>
require('react-native-worklets/lib/module/mock')
);

jest.mock('react-native-reanimated', () =>
require('react-native-reanimated/mock')
);
jest.mock('react-native-reanimated', () => {
const Reanimated = require('react-native-reanimated/mock');

// The mock doesn't ship the CSS easing helpers; stub the ones we use.
return {
...Reanimated,
cubicBezier: (...points) => `cubic-bezier(${points.join(', ')})`,
};
});

jest.mock('@react-native-vector-icons/material-design-icons', () => {
const React = require('react');
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"@release-it/conventional-changelog": "^1.1.0",
"@testing-library/react-native": "^14.0.0",
"@types/color": "^3.0.0",
"@types/jest": "^30.0.0",
"@types/node": "^24.0.0",
"@types/react": "^19.2.7",
"all-contributors-cli": "^6.24.0",
Expand Down
15 changes: 9 additions & 6 deletions src/components/FAB/Content.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { StyleSheet, View } from 'react-native';
import type { ColorValue, StyleProp, ViewStyle } from 'react-native';
import {
ColorValue,
StyleProp,
StyleSheet,
View,
ViewStyle,
} from 'react-native';

import Reanimated from 'react-native-reanimated';
import type { AnimatedStyle } from 'react-native-reanimated';
import Reanimated, { AnimatedStyle } from 'react-native-reanimated';

import type { TypescaleKey } from '../../theme/types';
import Icon from '../Icon';
import type { IconSource } from '../Icon';
import Icon, { IconSource } from '../Icon';
import AnimatedText from '../Typography/AnimatedText';

export type ContentProps = {
Expand Down
12 changes: 8 additions & 4 deletions src/components/FAB/Extended.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as React from 'react';
import { StyleSheet, View } from 'react-native';
import type {
import {
ColorValue,
GestureResponderEvent,
PressableAndroidRippleConfig,
StyleProp,
StyleSheet,
View,
ViewStyle,
} from 'react-native';

Expand All @@ -18,7 +19,7 @@ import Reanimated, {
import { scheduleOnUI } from 'react-native-worklets';

import Shell from './Shell';
import type { Size, Variant } from './tokens';
import { Size, Variant } from './tokens';
import { getDimensions } from './utils';
import { useInternalTheme } from '../../core/theming';
import { useReduceMotion } from '../../theme/accessibility/ReduceMotionContext';
Expand Down Expand Up @@ -110,7 +111,10 @@ export type Props = {
* @optional
*/
theme?: ThemeProp;
ref?: React.Ref<View>;
/**
* @optional
*/
ref?: React.RefObject<View>;
};

/**
Expand Down
38 changes: 34 additions & 4 deletions src/components/FAB/FAB.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import * as React from 'react';
import { View } from 'react-native';
import type {
import {
ColorValue,
GestureResponderEvent,
PressableAndroidRippleConfig,
StyleProp,
View,
ViewStyle,
} from 'react-native';

import Shell from './Shell';
import type { Size, Variant } from './tokens';
import { Size, Variant } from './tokens';
import type { ThemeProp } from '../../types';
import type { IconSource } from '../Icon';

Expand Down Expand Up @@ -43,6 +43,26 @@ export type Props = {
* Function to execute on press.
*/
onPress?: (e: GestureResponderEvent) => void;
/**
* Function to execute on long press.
*/
onLongPress?: (e: GestureResponderEvent) => void;
/**
* Function to execute when a touch is released.
*/
onPressOut?: (e: GestureResponderEvent) => void;
/**
* The number of milliseconds a user must touch the element before executing `onLongPress`.
*/
delayLongPress?: number;
/**
* Called when the pointer enters the element (web only).
*/
onHoverIn?: () => void;
/**
* Called when the pointer leaves the element (web only).
*/
onHoverOut?: () => void;
/**
* Accessibility label. Falls back to nothing if unset.
*/
Expand Down Expand Up @@ -82,7 +102,7 @@ export type Props = {
* @optional
*/
theme?: ThemeProp;
ref?: React.Ref<View>;
ref?: React.RefObject<View>;
};

/**
Expand Down Expand Up @@ -120,6 +140,11 @@ const FAB = ({
size = 'default',
visible = true,
onPress,
onLongPress,
onPressOut,
delayLongPress,
onHoverIn,
onHoverOut,
containerColor,
contentColor,
'aria-label': ariaLabel,
Expand All @@ -140,6 +165,11 @@ const FAB = ({
size={size}
visible={visible}
onPress={onPress}
onLongPress={onLongPress}
onPressOut={onPressOut}
delayLongPress={delayLongPress}
onHoverIn={onHoverIn}
onHoverOut={onHoverOut}
containerColor={containerColor}
contentColor={contentColor}
aria-label={ariaLabel}
Expand Down
Loading