Skip to content

Commit 422b8c4

Browse files
committed
Initial strategy config changes in docs
1 parent ecc4512 commit 422b8c4

4 files changed

Lines changed: 313 additions & 25 deletions

File tree

packages/docs/docs/customization/custom-ordering-strategies.md

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
---
2+
sidebar_position: 3
3+
description: 'Learn how to create custom sorting strategies'
4+
title: Custom Strategy
5+
---
6+
7+
import Tabs from '@theme/Tabs';
8+
import TabItem from '@theme/TabItem';
9+
10+
# Custom Ordering Strategy
11+
12+
While `react-native-sortables` comes with built-in strategies for Grid and Flex layouts, you might need specific behavior that isn't covered by the defaults. In such cases, you can implement a **Custom Sort Strategy**.
13+
14+
:::info
15+
16+
This guide describes advanced customization. Familiarity with [Reanimated worklets](https://docs.swmansion.com/react-native-reanimated/docs/2.x/worklets/) is recommended as strategies run on the UI thread.
17+
18+
:::
19+
20+
## What is a Strategy?
21+
22+
A strategy is a function that determines the **new order of items** based on the **active item's position**. It is called repeatedly while the user drags an item.
23+
24+
Technically, a strategy is defined by a **Factory Function** (`SortStrategyFactory`) which returns an **Updater Function** (`OrderUpdater`).
25+
26+
### `SortStrategyFactory`
27+
28+
This is a **React Hook** that runs on the JS thread. It is called once when the component mounts or when dependencies change. Its primary purpose is to **prepare data** (like layout values, item sizes) needed for the updater function.
29+
30+
```ts
31+
type SortStrategyFactory = () => OrderUpdater;
32+
```
33+
34+
### `OrderUpdater`
35+
36+
This is a **Worklet Function** that runs on the UI thread. It is called on every frame during the drag gesture.
37+
38+
```ts
39+
type OrderUpdater = (params: {
40+
activeKey: string;
41+
activeIndex: number;
42+
dimensions: { width: number; height: number };
43+
position: { x: number; y: number };
44+
}) => Array<string> | undefined | null;
45+
```
46+
47+
- **Returns**: A new array of item keys representing the new order, or `undefined`/`null` if the order hasn't changed.
48+
49+
---
50+
51+
## Tutorial: Simple Grid Strategy
52+
53+
Let's build a simplified strategy for a **Grid** layout where all items have the **same size**. We will implement a basic "swap" behavior: when an item is dragged over another item, they swap places.
54+
55+
### Step 1: Access Contexts
56+
57+
First, we need to access the layout information. `react-native-sortables` exposes several hooks for this:
58+
59+
- `useCommonValuesContext()`: Provides shared values like `indexToKey` (current order), `containerWidth`, `itemWidths`, etc.
60+
- `useGridLayoutContext()`: Specific to Grid, provides `columns`, `rows`, gaps, etc.
61+
62+
### Step 2: Implement the Factory
63+
64+
We create a factory hook that prepares the shared values.
65+
66+
```tsx
67+
import { useCommonValuesContext } from 'react-native-sortables';
68+
import { useGridLayoutContext } from 'react-native-sortables';
69+
70+
const useSimpleGridStrategy = () => {
71+
// 1. Get shared values from context
72+
const { indexToKey, itemWidths, itemHeights } = useCommonValuesContext();
73+
const { columns, isVertical } = useGridLayoutContext();
74+
75+
// 2. Return the worklet function
76+
return params => {
77+
'worklet';
78+
// Implementation will go here
79+
return undefined;
80+
};
81+
};
82+
```
83+
84+
### Step 3: Implement the Worklet
85+
86+
Inside the worklet, we calculate which index the dragged item is hovering over, and if it's different from the current index, we swap them.
87+
88+
```tsx
89+
// Helper to swap items in array
90+
function swap(array: string[], from: number, to: number) {
91+
'worklet';
92+
const newArray = [...array];
93+
[newArray[from], newArray[to]] = [newArray[to], newArray[from]];
94+
return newArray;
95+
}
96+
97+
// ... inside the returned function:
98+
return ({ activeIndex, position }) => {
99+
'worklet';
100+
101+
// 1. Calculate the target index based on position
102+
// (Simplified for this tutorial with constants)
103+
const COLUMNS = 3;
104+
const ITEM_WIDTH = 100;
105+
const ITEM_HEIGHT = 100;
106+
const GAP = 10;
107+
108+
// Approximate row and column from position
109+
const col = Math.floor(
110+
Math.max(0, position.x + ITEM_WIDTH / 2) / (ITEM_WIDTH + GAP)
111+
);
112+
const row = Math.floor(
113+
Math.max(0, position.y + ITEM_HEIGHT / 2) / (ITEM_HEIGHT + GAP)
114+
);
115+
116+
const targetIndex = row * COLUMNS + col;
117+
const currentOrder = indexToKey.value;
118+
119+
// boundary check
120+
if (targetIndex < 0 || targetIndex >= currentOrder.length) return;
121+
122+
// 2. If index changed, return new order
123+
if (targetIndex !== activeIndex) {
124+
return swap(currentOrder, activeIndex, targetIndex);
125+
}
126+
127+
return undefined; // No change
128+
};
129+
```
130+
131+
### Full Example
132+
133+
Here is the complete code for a simple strategy that assumes a fixed 3-column grid.
134+
135+
```tsx
136+
import { useCallback } from 'react';
137+
import {
138+
useCommonValuesContext,
139+
SortStrategyFactory
140+
} from 'react-native-sortables';
141+
142+
// Simple reorder helper
143+
function swap(array: string[], from: number, to: number) {
144+
'worklet';
145+
const newArray = [...array];
146+
[newArray[from], newArray[to]] = [newArray[to], newArray[from]];
147+
return newArray;
148+
}
149+
150+
export const useSimpleSwapStrategy: SortStrategyFactory = () => {
151+
const { indexToKey } = useCommonValuesContext();
152+
153+
return ({ activeIndex, position, dimensions }) => {
154+
'worklet';
155+
156+
const COLUMNS = 3;
157+
const ITEM_WIDTH = 100;
158+
const ITEM_HEIGHT = 100;
159+
const GAP = 10;
160+
161+
// Calculate index based on center position of the dragged item
162+
const centerX = position.x + dimensions.width / 2;
163+
const centerY = position.y + dimensions.height / 2;
164+
165+
const col = Math.floor(centerX / (ITEM_WIDTH + GAP));
166+
const row = Math.floor(centerY / (ITEM_HEIGHT + GAP));
167+
168+
const targetIndex = row * COLUMNS + col;
169+
const currentOrder = indexToKey.value;
170+
171+
if (
172+
targetIndex >= 0 &&
173+
targetIndex < currentOrder.length &&
174+
targetIndex !== activeIndex
175+
) {
176+
return swap(currentOrder, activeIndex, targetIndex);
177+
}
178+
179+
return undefined;
180+
};
181+
};
182+
183+
// Usage
184+
// <Sortable.Grid strategy={useSimpleSwapStrategy} ... />
185+
```
186+
187+
### Usage Example
188+
189+
Here is how you can use the custom strategy in a component.
190+
191+
```tsx
192+
import React, { useCallback } from 'react';
193+
import { StyleSheet, Text, View } from 'react-native';
194+
import Sortable, { SortableGridRenderItem } from 'react-native-sortables';
195+
import { useSimpleSwapStrategy } from './useSimpleSwapStrategy'; // inner file import
196+
197+
const DATA = Array.from({ length: 9 }, (_, i) => `Item ${i + 1}`);
198+
const COLUMNS = 3;
199+
200+
export default function App() {
201+
const renderItem = useCallback<SortableGridRenderItem<string>>(
202+
({ item }) => (
203+
<View style={styles.card}>
204+
<Text style={styles.text}>{item}</Text>
205+
</View>
206+
),
207+
[]
208+
);
209+
210+
return (
211+
<View style={styles.container}>
212+
<Sortable.Grid
213+
data={DATA}
214+
renderItem={renderItem}
215+
columns={COLUMNS}
216+
rowHeight={100}
217+
columnGap={10}
218+
rowGap={10}
219+
strategy={useSimpleSwapStrategy}
220+
/>
221+
</View>
222+
);
223+
}
224+
225+
const styles = StyleSheet.create({
226+
container: {
227+
flex: 1,
228+
padding: 20,
229+
justifyContent: 'center',
230+
backgroundColor: '#f5f5f5'
231+
},
232+
card: {
233+
flex: 1,
234+
height: 100, // Matches ITEM_HEIGHT in strategy
235+
justifyContent: 'center',
236+
alignItems: 'center',
237+
backgroundColor: 'white',
238+
borderRadius: 8,
239+
borderWidth: 1,
240+
borderColor: '#ddd'
241+
},
242+
text: {
243+
fontWeight: 'bold',
244+
fontSize: 16
245+
}
246+
});
247+
```
248+
249+
<div style={{ textAlign: 'center', margin: '20px 0' }}>
250+
{/* <video src={require('@site/static/video/custom-strategy-demo.mp4').default} autoPlay loop muted playsInline style={{ maxWidth: '100%', borderRadius: 8 }} /> */}
251+
<div
252+
style={{
253+
padding: '40px',
254+
backgroundColor: '#f0f0f0',
255+
borderRadius: '8px',
256+
border: '2px dashed #ccc',
257+
color: '#666'
258+
}}>
259+
Video Demo Placeholder
260+
</div>
261+
</div>
262+
263+
## Available Contexts
264+
265+
When building specific strategies, you can access detailed state from these contexts:
266+
267+
### `useCommonValuesContext`
268+
269+
- `indexToKey`: Current order of items.
270+
- `containerWidth` / `containerHeight`: Dimensions of the sortable container.
271+
- `itemWidths` / `itemHeights`: Dimensions of individual items.
272+
- `activeItemKey`: Key of the item currently being dragged.
273+
274+
### `useGridLayoutContext` (Grid Only)
275+
276+
- `columns` / `rows`: Number of columns/rows.
277+
- `gap` / `rowGap` / `columnGap`: Spacing configuration.
278+
- `isVertical`: Orientation of the grid.
279+
280+
### `useFlexLayoutContext` (Flex Only)
281+
282+
- `flexDirection`: Layout direction.
283+
- `flexWrap`: Wrap behavior.
284+
285+
:::tip
286+
287+
Always use the `'worklet'` directive in your updater function and helper functions to ensure they run smoothly on the UI thread.
288+
289+
:::

packages/docs/docs/flex/props.mdx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,10 +348,10 @@ Controls how items **are reordered** while the **active item** is being **dragge
348348
<summary>Type definitions</summary>
349349

350350
```tsx
351-
type SortableFlexStrategy = 'insert' | SortableFlexStrategyFactory;
351+
type SortableFlexStrategy = 'insert' | SortStrategyFactory;
352352
```
353353

354-
{/* TODO: Add link to docs page explaining how to use SortableFlexStrategyFactory */}
354+
[Read more about custom strategies](/customization/strategy)
355355

356356
</details>
357357

@@ -520,6 +520,16 @@ You can provide a **single number**, which will be used for both **top** and **b
520520

521521
---
522522

523+
### autoScrollMaxOverscroll
524+
525+
The maximum distance (in pixels) that the scrollable container can be overscrolled during auto-scrolling.
526+
527+
| type | default | required |
528+
| ---------------------------------------- | ------- | -------- |
529+
| `Animatable<[number, number] \| number>` | 50 | NO |
530+
531+
---
532+
523533
### autoScrollSpeed
524534

525535
Speed of the auto scroll. Adjust it based on your preferences.

packages/docs/docs/grid/props.mdx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,10 @@ Controls how items **are reordered** while the **active item** is being **dragge
216216
<summary>Type definitions</summary>
217217

218218
```tsx
219-
type SortableGridStrategy = 'insert' | 'swap' | SortableGridStrategyFactory;
219+
type SortableGridStrategy = 'insert' | 'swap' | SortStrategyFactory;
220220
```
221221

222-
{/* TODO: Add link to docs page explaining how to use SortableGridStrategyFactory */}
222+
[Read more about custom strategies](/customization/strategy)
223223

224224
</details>
225225

@@ -387,6 +387,16 @@ You can provide a **single number**, which will be used for both **top** and **b
387387

388388
---
389389

390+
### autoScrollMaxOverscroll
391+
392+
The maximum distance (in pixels) that the scrollable container can be overscrolled during auto-scrolling.
393+
394+
| type | default | required |
395+
| ---------------------------------------- | ------- | -------- |
396+
| `Animatable<[number, number] \| number>` | 50 | NO |
397+
398+
---
399+
390400
### autoScrollSpeed
391401

392402
Speed of the auto scroll. Adjust it based on your preferences.

0 commit comments

Comments
 (0)