Skip to content
Open
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
54 changes: 54 additions & 0 deletions apps/common-app/src/new_api/components/touchable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,60 @@ export default function TouchableExample() {
</View>
</View>

<View style={styles.section}>
<Text style={styles.sectionHeader}>Hover states</Text>
<Text>Hover-only feedback (web).</Text>

Comment thread
j-piasecki marked this conversation as resolved.
<View style={styles.row}>
<TouchableWrapper
name="Opacity"
color={COLORS.WEB_BLUE}
hoverOpacity={0.7}
/>

<TouchableWrapper
name="Scale"
color={COLORS.RED}
hoverScale={1.05}
/>

<TouchableWrapper
name="Underlay"
color={COLORS.YELLOW}
hoverUnderlayOpacity={0.3}
underlayColor={COLORS.DARK_GREEN}
/>
</View>

<Text>Hover + press combined.</Text>

<View style={styles.row}>
<TouchableWrapper
name="Opacity"
color={COLORS.LIGHT_BLUE}
hoverOpacity={0.85}
activeOpacity={0.6}
/>

<TouchableWrapper
name="Scale"
color={COLORS.DARK_SALMON}
hoverScale={1.05}
activeScale={0.95}
/>

<TouchableWrapper
name="All"
color={COLORS.NAVY}
hoverScale={1.05}
hoverUnderlayOpacity={0.2}
activeScale={0.95}
activeUnderlayOpacity={0.5}
underlayColor={COLORS.DARK_GREEN}
/>
</View>
</View>

<View style={styles.section}>
<Text style={styles.sectionHeader}>Android ripple</Text>
<Text>Configurable ripple effect on Touchable component.</Text>
Expand Down
56 changes: 56 additions & 0 deletions packages/docs-gesture-handler/docs/components/touchable.mdx
Comment thread
m-bert marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,22 @@ defaultOpacity?: number;

Defines the opacity of the whole component when the button is active. By default set to `1`.

### activeScale

```ts
activeScale?: number;
```

Defines the scale of the whole component when the button is active.

### defaultScale

```ts
defaultScale?: number;
```

Defines the scale of the whole component when the button is inactive. By default set to `1`.

### activeUnderlayOpacity

```ts
Expand All @@ -188,6 +204,46 @@ defaultUnderlayOpacity?: number;

Defines the initial opacity of underlay when the button is inactive. By default set to `0`.

<Badges platforms={['web']}>
### hoverOpacity
</Badges>

```ts
hoverOpacity?: number;
```

Defines the opacity of the whole component when the button is hovered. By default falls back to [`defaultOpacity`](#defaultopacity).

<Badges platforms={['web']}>
### hoverScale
</Badges>

```ts
hoverScale?: number;
```

Defines the scale of the whole component when the button is hovered. By default falls back to [`defaultScale`](#defaultscale).

<Badges platforms={['web']}>
### hoverUnderlayOpacity
</Badges>

```ts
hoverUnderlayOpacity?: number;
```

Defines the opacity of the underlay when the button is hovered. By default falls back to [`defaultUnderlayOpacity`](#defaultunderlayopacity).

<Badges platforms={['web']}>
### hoverAnimationDuration
</Badges>

```ts
hoverAnimationDuration?: number;
```

Duration of the hover animation, in milliseconds. By default falls back to [`tapAnimationDuration`](#tapanimationduration).

### underlayColor

```ts
Expand Down
Comment thread
m-bert marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,38 @@ export interface ButtonProps extends ViewProps, AccessibilityProps {
*/
activeUnderlayOpacity?: number | undefined;

/**
* Web only.
*
* Opacity applied to the button when it is hovered. Defaults to
* `defaultOpacity` when not set.
*/
hoverOpacity?: number | undefined;
Comment thread
j-piasecki marked this conversation as resolved.

/**
* Web only.
*
* Scale applied to the button when it is hovered. Defaults to
* `defaultScale` when not set.
*/
hoverScale?: number | undefined;

/**
* Web only.
*
* Opacity applied to the underlay when the button is hovered. Defaults
* to `defaultUnderlayOpacity` when not set.
*/
hoverUnderlayOpacity?: number | undefined;

/**
* Web only.
*
* Duration of the hover animation, in milliseconds. Defaults to
* `tapAnimationDuration` when not set (or set to any negative value).
*/
hoverAnimationDuration?: number | undefined;

/**
* Opacity applied to the button when it is not pressed.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ type ButtonProps = ViewProps & {
enabled?: boolean;
pressAndHoldAnimationDuration?: number;
tapAnimationDuration?: number;
hoverAnimationDuration?: number;
activeOpacity?: number;
activeScale?: number;
activeUnderlayOpacity?: number;
hoverOpacity?: number;
hoverScale?: number;
hoverUnderlayOpacity?: number;
defaultOpacity?: number;
defaultScale?: number;
defaultUnderlayOpacity?: number;
Expand All @@ -23,9 +27,13 @@ export const ButtonComponent = ({
enabled = true,
pressAndHoldAnimationDuration: pressAndHoldAnimationDurationProp = -1,
tapAnimationDuration: tapAnimationDurationProp = 100,
hoverAnimationDuration: hoverAnimationDurationProp = -1,
activeOpacity = 1,
activeScale = 1,
activeUnderlayOpacity = 0,
hoverOpacity: hoverOpacityProp,
hoverScale: hoverScaleProp,
hoverUnderlayOpacity: hoverUnderlayOpacityProp,
defaultOpacity = 1,
defaultScale = 1,
defaultUnderlayOpacity = 0,
Expand All @@ -40,8 +48,18 @@ export const ButtonComponent = ({
pressAndHoldAnimationDurationProp < 0
? tapAnimationDuration
: pressAndHoldAnimationDurationProp;
const hoverAnimationDuration =
hoverAnimationDurationProp < 0
? tapAnimationDuration
Comment thread
m-bert marked this conversation as resolved.
: hoverAnimationDurationProp;

const hoverOpacity = hoverOpacityProp ?? defaultOpacity;
const hoverScale = hoverScaleProp ?? defaultScale;
const hoverUnderlayOpacity =
hoverUnderlayOpacityProp ?? defaultUnderlayOpacity;

const [pressed, setPressed] = React.useState(false);
const [hovered, setHovered] = React.useState(false);
const [currentDuration, setCurrentDuration] = React.useState(
pressAndHoldAnimationDuration
);
Expand Down Expand Up @@ -156,14 +174,58 @@ export const ButtonComponent = ({
[pressAndHoldAnimationDuration, tapAnimationDuration]
);

const handlePointerEnter = React.useCallback(
(event: NativeSyntheticEvent<{ pointerType?: string }>) => {
if (!enabled || event.nativeEvent.pointerType === 'touch') {
Comment thread
m-bert marked this conversation as resolved.
return;
}
// Skip duration update while pressed so the press transition owns it.
if (!pressed) {
setCurrentDuration(hoverAnimationDuration);
}
setHovered(true);
},
[enabled, pressed, hoverAnimationDuration]
);

const handlePointerLeave = React.useCallback(
(event: NativeSyntheticEvent<{ pointerType?: string }>) => {
pressOut(event);
Comment thread
m-bert marked this conversation as resolved.
if (event.nativeEvent.pointerType === 'touch') {
Comment thread
m-bert marked this conversation as resolved.
return;
}
if (!pressed) {
setCurrentDuration(hoverAnimationDuration);
}
setHovered(false);
},
[pressOut, pressed, hoverAnimationDuration]
);

// Mask hover at render rather than clearing the state. Avoids a state
// write inside an effect, and lets hover resume naturally when `enabled`
// flips back to true while the pointer is still inside.
const effectiveHovered = hovered && enabled;

const currentUnderlayOpacity = pressed
? activeUnderlayOpacity
: defaultUnderlayOpacity;
: effectiveHovered
? hoverUnderlayOpacity
: defaultUnderlayOpacity;
const hasUnderlay = underlayColor != null;
const hasOpacity = activeOpacity !== 1 || defaultOpacity !== 1;
const currentOpacity = pressed ? activeOpacity : defaultOpacity;
const hasScale = activeScale !== 1 || defaultScale !== 1;
const currentScale = pressed ? activeScale : defaultScale;
const hasOpacity =
activeOpacity !== 1 || hoverOpacity !== 1 || defaultOpacity !== 1;
const currentOpacity = pressed
? activeOpacity
: effectiveHovered
? hoverOpacity
: defaultOpacity;
const hasScale = activeScale !== 1 || hoverScale !== 1 || defaultScale !== 1;
const currentScale = pressed
? activeScale
: effectiveHovered
? hoverScale
: defaultScale;

const easing = 'cubic-bezier(0.5, 1, 0.89, 1)';
const transitionProps: string[] = [];
Expand All @@ -177,6 +239,7 @@ export const ButtonComponent = ({

return (
<View
{...rest}
ref={setRef}
accessibilityRole="button"
style={[
Expand All @@ -190,11 +253,11 @@ export const ButtonComponent = ({
...(hasUnderlay && { overflow: 'hidden' }),
},
]}
onPointerEnter={handlePointerEnter}
onPointerDown={pressIn}
onPointerUp={pressOut}
onPointerCancel={pressOut}
onPointerLeave={pressOut}
{...rest}>
onPointerLeave={handlePointerLeave}>
{hasUnderlay && (
<View
style={{
Expand Down
Loading