Skip to content

[Docs] Touchable#4022

Open
m-bert wants to merge 82 commits intomainfrom
@mbert/clickable-docs
Open

[Docs] Touchable#4022
m-bert wants to merge 82 commits intomainfrom
@mbert/clickable-docs

Conversation

@m-bert
Copy link
Copy Markdown
Contributor

@m-bert m-bert commented Mar 12, 2026

Description

This PR adds docs for Clickable component

Test plan

Read docs 🤓

m-bert added a commit that referenced this pull request Apr 1, 2026
## Description

This PR introduces new `Clickable` component, which is meant to be a
replacement for buttons.

> [!NOTE]
> Docs for `Clickable` will be added in #4022, as I don't want to
release them right away after merging this PR.

### `borderless`

For now, `borderless` doesn't work. I've tested clickable with some
changes that allow `borderless` ripple to be visible, however we don't
want to introduce them here because it would break other things. Also it
should be generl fix, not in the PR with new component.

## Stress test

Render list with 2000 buttons 25 times (50ms delay between renders),
drop 3 best and 3 worst results. Then calculate average.

Stress test example is available in this PR. 

#### Android

|           | $t_{Button}$ | $t_{Clickable}$ | $\Delta{t}$  |
|------------------|--------------|-----------------|--------------|
| `BaseButton`       | 1196,18      | 1292,3          | 96,12        |
| `RectButton`       | 1672,6       | 1275,68         | -396,92      |
| `BorderlessButton` | 1451,34      | 1290,74         | -160,6       |

#### iOS

|               | $t_{Button}$ | $t_{Clickable}$ | $\Delta{t}$  |
|------------------|--------------|-----------------|--------------|
| `BaseButton`       | 1101,37      | 1154,6          | 53,23        |
| `RectButton`      | 1528,07      | 1160,07         | -368         |
| `BorderlessButton` | 1330,24      | 1172,69         | -157,55      |


#### Web

|            | $t_{Button}$ | $t_{Clickable}$ | $\Delta{t}$  |
|------------------|--------------|-----------------|--------------|
| `BaseButton`       | 64,18        | 95,57           | 31,39        |
| `RectButton`       | 104,58       | 97,95           | -6,63        |
| `BorderlessButton` | 81,11        | 98,64           | 17,53        |

## Test plan

New examples

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Base automatically changed from @mbert/clickable to main April 1, 2026 15:11
@m-bert m-bert requested a review from j-piasecki April 3, 2026 10:19

`PureNativeButton` has been removed. If encountered, inform the user that it has been removed and let them decide how to handle that case. They can achieve similar functionality with other buttons.

When migrating buttons, you should use `Clickable` component instead. To replace `BaseButton` use `Clickable` with default props, to replace `RectButton` use `Clickable` with `underlayActiveOpacity={0.105}` and to replace `BorderlessButton` use `Clickable` with `activeOpacity={0.3}`.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It may also be worth adding how to replace deprecated Touchables with the new component.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Okay, what about d3c4337?

Also, I've tested it on the code below and seems like there are some android-related issues, I'll check what it is and let you know later.

Test code
import React from 'react';
import { StyleSheet, Text, View, ScrollView, Platform } from 'react-native';
import {
  GestureHandlerRootView,
  Clickable,
  TouchableOpacity,
  TouchableHighlight,
  TouchableWithoutFeedback,
  TouchableNativeFeedback,
} from 'react-native-gesture-handler';
import { COLORS } from '../../../common';

function SectionHeader({ children }: { children: string }) {
  return <Text style={styles.sectionHeader}>{children}</Text>;
}

function Label({ children }: { children: string }) {
  return <Text style={styles.label}>{children}</Text>;
}

function ComparisonRow({
  before,
  after,
}: {
  before: React.ReactNode;
  after: React.ReactNode;
}) {
  return (
    <View style={styles.comparisonRow}>
      <View style={styles.comparisonColumn}>
        <Text style={styles.comparisonLabel}>Before</Text>
        {before}
      </View>
      <View style={styles.comparisonColumn}>
        <Text style={styles.comparisonLabel}>After</Text>
        {after}
      </View>
    </View>
  );
}

function ButtonContent({ text, color }: { text: string; color: string }) {
  return (
    <View style={[styles.button, { backgroundColor: color }]}>
      <Text style={styles.buttonText}>{text}</Text>
    </View>
  );
}

export default function ClickableTouchableMigrationExample() {
  return (
    <GestureHandlerRootView style={styles.container}>
      <ScrollView contentContainerStyle={styles.scrollContent}>
        {/* TouchableOpacity → Clickable */}
        <View style={styles.section}>
          <SectionHeader>TouchableOpacity → Clickable</SectionHeader>
          <Label>Uses activeOpacity to dim the component on press.</Label>
          <ComparisonRow
            before={
              <TouchableOpacity
                onPress={() => console.log('TouchableOpacity pressed')}>
                <ButtonContent text="TouchableOpacity" color={COLORS.NAVY} />
              </TouchableOpacity>
            }
            after={
              <Clickable
                activeOpacity={0.2}
                onPress={() => console.log('Clickable (opacity) pressed')}>
                <ButtonContent text="Clickable" color={COLORS.NAVY} />
              </Clickable>
            }
          />
        </View>

        {/* TouchableHighlight → Clickable */}
        <View style={styles.section}>
          <SectionHeader>TouchableHighlight → Clickable</SectionHeader>
          <Label>
            Uses underlayColor and activeUnderlayOpacity to show a colored
            underlay on press.
          </Label>
          <ComparisonRow
            before={
              <TouchableHighlight
                // underlayColor={COLORS.KINDA_BLUE}
                onPress={() => console.log('TouchableHighlight pressed')}>
                <ButtonContent
                  text="TouchableHighlight"
                  color={COLORS.WEB_BLUE}
                />
              </TouchableHighlight>
            }
            after={
              <Clickable
                // underlayColor={COLORS.KINDA_BLUE}
                activeUnderlayOpacity={1}
                onPress={() => console.log('Clickable (highlight) pressed')}>
                <ButtonContent text="Clickable" color={COLORS.WEB_BLUE} />
              </Clickable>
            }
          />
        </View>

        {/* TouchableWithoutFeedback → Clickable */}
        <View style={styles.section}>
          <SectionHeader>TouchableWithoutFeedback → Clickable</SectionHeader>
          <Label>No visual feedback — just handles press events.</Label>
          <ComparisonRow
            before={
              <TouchableWithoutFeedback
                onPress={() => console.log('TouchableWithoutFeedback pressed')}>
                <ButtonContent
                  text="WithoutFeedback"
                  color={COLORS.DARK_GREEN}
                />
              </TouchableWithoutFeedback>
            }
            after={
              <Clickable
                onPress={() => console.log('Clickable (no feedback) pressed')}>
                <ButtonContent text="Clickable" color={COLORS.DARK_GREEN} />
              </Clickable>
            }
          />
        </View>

        {/* TouchableNativeFeedback → Clickable (Android only) */}
        {Platform.OS === 'android' && (
          <View style={styles.section}>
            <SectionHeader>TouchableNativeFeedback → Clickable</SectionHeader>
            <Label>
              Uses androidRipple to show the native ripple effect (Android
              only).
            </Label>
            <ComparisonRow
              before={
                <TouchableNativeFeedback
                  // background={TouchableNativeFeedback.Ripple('#aaa', false)}
                  onPress={() =>
                    console.log('TouchableNativeFeedback pressed')
                  }>
                  <ButtonContent text="NativeFeedback" color={COLORS.ANDROID} />
                </TouchableNativeFeedback>
              }
              after={
                <Clickable
                  androidRipple={{
                    foreground: true,
                  }}
                  onPress={() => console.log('Clickable (ripple) pressed')}>
                  <ButtonContent text="Clickable" color={COLORS.ANDROID} />
                </Clickable>
              }
            />
          </View>
        )}
      </ScrollView>
    </GestureHandlerRootView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  scrollContent: {
    paddingBottom: 40,
  },
  section: {
    padding: 20,
    borderBottomWidth: StyleSheet.hairlineWidth,
    borderBottomColor: '#ccc',
  },
  sectionHeader: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 4,
  },
  label: {
    fontSize: 13,
    color: '#666',
    marginBottom: 12,
  },
  comparisonRow: {
    flexDirection: 'row',
    gap: 12,
  },
  comparisonColumn: {
    flex: 1,
    alignItems: 'center',
  },
  comparisonLabel: {
    fontSize: 12,
    fontWeight: '600',
    color: '#999',
    marginBottom: 8,
    textTransform: 'uppercase',
  },
  button: {
    width: '100%',
    height: 50,
    borderRadius: 12,
    alignItems: 'center',
    justifyContent: 'center',
  },
  buttonText: {
    color: 'white',
    fontSize: 13,
    fontWeight: '600',
  },
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

  1. Add this to the LLM skill as well
  2. This will read weird if we settle on Touchable as a name

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ad 1. 46eba22

Ad 2. I agree. I've changed this section (it is not commited though, I've put this below so you can read it). Let me know what you think.

Rewritten section

Migrating from legacy Touchable variants

If you were using the specialized touchable components (TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback, or TouchableNativeFeedback), you can replicate their behavior with the unified Touchable component.

TouchableOpacity

To replicate TouchableOpacity behavior, add activeOpacity={0.2}.

<Touchable
  ...
  activeOpacity={0.2}/>

TouchableHighlight

To replicate TouchableHighlight behavior, add activeUnderlayOpacity={1}.

<Touchable
  ...
  activeUnderlayOpacity={1}/>

TouchableWithoutFeedback

To replicate TouchableWithoutFeedback behavior, use a plain Touchable.

<Touchable ... />

TouchableNativeFeedback

To replicate TouchableNativeFeedback behavior, use the androidRipple prop. Make sure to set foreground={true}.

<Touchable
  ...
  androidRipple={{
    foreground: true,
  }}/>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Okay, following #4063 I've renamed Clickable to Touchable

m-bert and others added 13 commits April 7, 2026 12:51
Co-authored-by: Jakub Piasecki <jakub.piasecki@swmansion.com>
Co-authored-by: Jakub Piasecki <jakub.piasecki@swmansion.com>
…eact-native-gesture-handler into @mbert/clickable-docs
Co-authored-by: Jakub Piasecki <jakub.piasecki@swmansion.com>
Co-authored-by: Jakub Piasecki <jakub.piasecki@swmansion.com>
Co-authored-by: Jakub Piasecki <jakub.piasecki@swmansion.com>
Co-authored-by: Jakub Piasecki <jakub.piasecki@swmansion.com>
…eact-native-gesture-handler into @mbert/clickable-docs
@m-bert m-bert requested a review from j-piasecki April 7, 2026 12:01
@m-bert m-bert changed the title [Docs] Clickable [Docs] Touchable Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Documentation Documentation change/enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants