Skip to content

Commit 7536391

Browse files
feat: make slider snap
1 parent 5a39278 commit 7536391

2 files changed

Lines changed: 131 additions & 6 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import React from 'react';
2+
import { fireEvent, render } from '@testing-library/react-native';
3+
4+
import {
5+
BatchSellPercentageSlider,
6+
SNAP_POINTS,
7+
snapToPercentageStep,
8+
} from './BatchSellPercentageSlider';
9+
10+
const SLIDER_TEST_ID = 'batch-sell-percentage-slider';
11+
12+
jest.mock('@metamask/design-system-twrnc-preset', () => ({
13+
useTailwind: () => ({
14+
style: () => ({}),
15+
}),
16+
}));
17+
18+
jest.mock('react-native-gesture-handler', () => {
19+
const { View } = jest.requireActual('react-native');
20+
const gesture = {
21+
onEnd: jest.fn().mockReturnThis(),
22+
onUpdate: jest.fn().mockReturnThis(),
23+
};
24+
25+
return {
26+
GestureHandlerRootView: View,
27+
GestureDetector: ({ children }: { children: React.ReactNode }) => children,
28+
Gesture: {
29+
Pan: jest.fn(() => gesture),
30+
Tap: jest.fn(() => gesture),
31+
Simultaneous: jest.fn((...gestures) => gestures),
32+
},
33+
};
34+
});
35+
36+
describe('BatchSellPercentageSlider', () => {
37+
it.each([
38+
[-10, 0],
39+
[0, 0],
40+
[12, 0],
41+
[13, 25],
42+
[37, 25],
43+
[38, 50],
44+
[62, 50],
45+
[63, 75],
46+
[87, 75],
47+
[88, 100],
48+
[120, 100],
49+
])('snaps %s to %s', (value, expectedValue) => {
50+
const result = snapToPercentageStep(value);
51+
52+
expect(result).toBe(expectedValue);
53+
});
54+
55+
it('increments accessibility value by one snap point', () => {
56+
const onValueChange = jest.fn();
57+
const { getByTestId } = render(
58+
<BatchSellPercentageSlider
59+
value={50}
60+
onValueChange={onValueChange}
61+
testID={SLIDER_TEST_ID}
62+
/>,
63+
);
64+
65+
fireEvent(getByTestId(SLIDER_TEST_ID), 'accessibilityAction', {
66+
nativeEvent: { actionName: 'increment' },
67+
});
68+
69+
expect(onValueChange).toHaveBeenCalledWith(75);
70+
});
71+
72+
it('decrements accessibility value by one snap point', () => {
73+
const onValueChange = jest.fn();
74+
const { getByTestId } = render(
75+
<BatchSellPercentageSlider
76+
value={50}
77+
onValueChange={onValueChange}
78+
testID={SLIDER_TEST_ID}
79+
/>,
80+
);
81+
82+
fireEvent(getByTestId(SLIDER_TEST_ID), 'accessibilityAction', {
83+
nativeEvent: { actionName: 'decrement' },
84+
});
85+
86+
expect(onValueChange).toHaveBeenCalledWith(25);
87+
});
88+
89+
it('renders muted marker dots for each snap point', () => {
90+
const { getByTestId } = render(
91+
<BatchSellPercentageSlider
92+
value={50}
93+
onValueChange={jest.fn()}
94+
testID={SLIDER_TEST_ID}
95+
/>,
96+
);
97+
98+
SNAP_POINTS.forEach((snapPoint) => {
99+
expect(
100+
getByTestId(`${SLIDER_TEST_ID}-snap-point-${snapPoint}`),
101+
).toBeOnTheScreen();
102+
});
103+
});
104+
});

app/components/UI/Bridge/Views/BatchSellReview/BatchSellPercentageSlider.tsx

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,14 @@ import Animated, {
1313
import { useTailwind } from '@metamask/design-system-twrnc-preset';
1414

1515
const HANDLE_SIZE = 24;
16-
const ACCESSIBILITY_STEP = 1;
16+
const MARKER_SIZE = 4;
17+
const PERCENTAGE_STEP = 25;
18+
export const SNAP_POINTS = [0, 25, 50, 75, 100];
19+
20+
export function snapToPercentageStep(value: number): number {
21+
const snappedValue = Math.round(value / PERCENTAGE_STEP) * PERCENTAGE_STEP;
22+
return Math.max(0, Math.min(100, snappedValue));
23+
}
1724

1825
interface BatchSellPercentageSliderProps {
1926
value: number;
@@ -33,8 +40,8 @@ export function BatchSellPercentageSlider({
3340

3441
const updatePosition = useCallback(
3542
(nextValue: number, width = widthRef.current) => {
36-
const clampedValue = Math.max(0, Math.min(100, nextValue));
37-
translateX.value = (clampedValue / 100) * width;
43+
const snappedValue = snapToPercentageStep(nextValue);
44+
translateX.value = (snappedValue / 100) * width;
3845
},
3946
[translateX],
4047
);
@@ -46,7 +53,7 @@ export function BatchSellPercentageSlider({
4653
}
4754

4855
const clampedPosition = Math.max(0, Math.min(position, width));
49-
const nextValue = Math.round((clampedPosition / width) * 100);
56+
const nextValue = snapToPercentageStep((clampedPosition / width) * 100);
5057

5158
updatePosition(nextValue, width);
5259
onValueChange(nextValue);
@@ -99,8 +106,8 @@ export function BatchSellPercentageSlider({
99106
(event: AccessibilityActionEvent) => {
100107
const nextValue =
101108
event.nativeEvent.actionName === 'increment'
102-
? Math.min(value + ACCESSIBILITY_STEP, 100)
103-
: Math.max(value - ACCESSIBILITY_STEP, 0);
109+
? snapToPercentageStep(value + PERCENTAGE_STEP)
110+
: snapToPercentageStep(value - PERCENTAGE_STEP);
104111

105112
onValueChange(nextValue);
106113
},
@@ -132,6 +139,20 @@ export function BatchSellPercentageSlider({
132139
progressStyle,
133140
]}
134141
/>
142+
{SNAP_POINTS.map((snapPoint) => (
143+
<Animated.View
144+
key={snapPoint}
145+
pointerEvents="none"
146+
testID={testID ? `${testID}-snap-point-${snapPoint}` : undefined}
147+
style={[
148+
tw.style('absolute h-1 w-1 rounded-full bg-icon-muted'),
149+
{
150+
left: `${snapPoint}%`,
151+
transform: [{ translateX: -MARKER_SIZE / 2 }],
152+
},
153+
]}
154+
/>
155+
))}
135156
<Animated.View
136157
style={[
137158
tw.style('absolute h-6 w-6 rounded-full bg-icon-default'),

0 commit comments

Comments
 (0)