Skip to content

Commit

Permalink
feat: support nested lists
Browse files Browse the repository at this point in the history
  • Loading branch information
omahili committed Dec 15, 2024
1 parent ee505ed commit 005dd07
Show file tree
Hide file tree
Showing 26 changed files with 1,116 additions and 197 deletions.
31 changes: 28 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
![NPM Downloads](https://img.shields.io/npm/dm/react-native-reorderable-list)
![GitHub License](https://img.shields.io/github/license/omahili/react-native-reorderable-list)
![NPM Version](https://img.shields.io/npm/v/react-native-reorderable-list)
<br />
![iOS](https://img.shields.io/badge/platform-iOS-000.svg?logo=apple)
![Android](https://img.shields.io/badge/platform-Android-3ddc84.svg?logo=android)

# React Native Reorderable List

A reorderable list for React Native applications, powered by Reanimated 🚀
Expand All @@ -11,6 +18,8 @@ A reorderable list for React Native applications, powered by Reanimated 🚀
- [Components](#components)
- [ReorderableList](#reorderablelist)
- [ReorderableListItem](#reorderablelistitem)
- [ScrollViewContainer](#scrollviewcontainer)
- [NestedReorderableList](#nestedreorderablelist)
- [Hooks](#hooks)
- [useReorderableDrag](#usereorderabledrag)
- [useReorderableDragStart](#usereorderabledragstart)
Expand Down Expand Up @@ -72,6 +81,22 @@ This component allows you to animate the item when it's dragged. It currently su
| scaleAnimationConfig | `{ enabled?: boolean, valueEnd?: number, valueStart?: number, easingEnd?: EasingFunction, easingStart?: EasingFunction, duration?: number }` | false | `{ enabled: true, valueEnd: 1, valueStart: 1.025, easingStart: Easing.in(Easing.ease), easingEnd: Easing.out(Easing.ease), duration: 200 }` | Configures the scale animation of the reorderable item. |
| opacityAnimationConfig | `{ enabled?: boolean, valueEnd?: number, valueStart?: number, easingEnd?: EasingFunction, easingStart?: EasingFunction, duration?: number }` | false | `{ enabled: true, valueEnd: 1, valueStart: 0.75, easingStart: Easing.in(Easing.ease), easingEnd: Easing.out(Easing.ease), duration: 200, }` | Configures the opacity animation of the reorderable item. |

### ScrollViewContainer

This component extends the [ScrollView](https://reactnative.dev/docs/scrollview) component and is used for nesting a [NestedReorderableList](#nestedreorderablelist) within a scrollable container:

| Props | Type | Required | Default | Description |
| -------- | ------------------------------------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| onScroll | `(event: NativeScrollEvent) => void` | No | N/A | Event fired at most once per frame during scrolling. Needs to be a `worklet`. See [Reanimated docs](https://docs.swmansion.com/react-native-reanimated) for further info. |

### NestedReorderableList

This component allows nesting a reorderable list within a [ScrollViewContainer](#scrollviewcontainer):

| Props | Type | Required | Default | Description |
| ---------- | --------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| scrollable | `boolean` | No | false | Whether the nested list is scrollable or not. If the nested list has a fixed height and it's scrollable it should be set to `true`, otherwise `false`. |

## Hooks

### useReorderableDrag
Expand Down Expand Up @@ -106,7 +131,7 @@ This hook allows handling the drag end event of a list item. It receives a workl

## Example

Here is an example of how to use this component. More examples can be found in the `example` directory of the repository.
Here is simple example of how to use this component. Examples of nested lists and much more can be found in the [example](https://github.com/omahili/react-native-reorderable-list/tree/feat/nested-lists/example) directory.

```typescript
import React, {useState} from 'react';
Expand Down Expand Up @@ -149,7 +174,7 @@ const Card: React.FC<CardProps> = React.memo(({id, color, height}) => {
);
});

const App = () => {
const Example = () => {
const [data, setData] = useState(list);

const renderItem = ({item}: ListRenderItemInfo<CardProps>) => (
Expand Down Expand Up @@ -186,7 +211,7 @@ const styles = StyleSheet.create({
},
});

export default App;
export default Example;
```
## License
Expand Down
98 changes: 98 additions & 0 deletions example/screens/HeaderFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React, {memo, useState} from 'react';
import {
ListRenderItemInfo,
Pressable,
StyleSheet,
Text,
View,
} from 'react-native';

import ReorderableList, {
ReorderableListItem,
ReorderableListReorderEvent,
reorderItems,
useReorderableDrag,
} from 'react-native-reorderable-list';

interface CardProps {
id: string;
color: string;
height: number;
}

const list: CardProps[] = [
{id: '0', color: 'red', height: 100},
{id: '1', color: 'blue', height: 150},
{id: '2', color: 'green', height: 80},
{id: '3', color: 'violet', height: 100},
{id: '4', color: 'orange', height: 120},
{id: '5', color: 'coral', height: 100},
{id: '6', color: 'purple', height: 110},
{id: '7', color: 'chocolate', height: 80},
{id: '8', color: 'crimson', height: 90},
{id: '9', color: 'seagreen', height: 90},
];

const Card: React.FC<CardProps> = memo(({id, color, height}) => {
const drag = useReorderableDrag();

return (
<ReorderableListItem>
<Pressable style={[styles.card, {height}]} onLongPress={drag}>
<Text style={[styles.text, {color}]}>Card {id}</Text>
</Pressable>
</ReorderableListItem>
);
});

export const HeaderFooterScreen = () => {
const [data, setData] = useState(list);

const renderItem = ({item}: ListRenderItemInfo<CardProps>) => (
<Card {...item} />
);

const handleReorder = ({from, to}: ReorderableListReorderEvent) => {
const newData = reorderItems(data, from, to);
setData(newData);
};

return (
<ReorderableList
data={data}
onReorder={handleReorder}
renderItem={renderItem}
keyExtractor={item => item.id}
ListHeaderComponent={
<View style={styles.block}>
<Text style={styles.text}>Header</Text>
</View>
}
ListFooterComponent={
<View style={styles.block}>
<Text style={styles.text}>Footer</Text>
</View>
}
/>
);
};

const styles = StyleSheet.create({
card: {
justifyContent: 'center',
alignItems: 'center',
margin: 6,
borderRadius: 5,
backgroundColor: 'white',
borderWidth: 1,
borderColor: '#ddd',
},
text: {
fontSize: 20,
},
block: {
height: 70,
alignItems: 'center',
justifyContent: 'center',
},
});
81 changes: 81 additions & 0 deletions example/screens/MultipleLists/List.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, {memo, useState} from 'react';
import {ListRenderItemInfo, Pressable, StyleSheet, Text} from 'react-native';

import ReorderableList, {
ReorderableListItem,
ReorderableListReorderEvent,
reorderItems,
useReorderableDrag,
} from 'react-native-reorderable-list';

interface CardProps {
id: string;
color: string;
height: number;
}

const list: CardProps[] = [
{id: '0', color: 'red', height: 100},
{id: '1', color: 'blue', height: 150},
{id: '2', color: 'green', height: 80},
{id: '3', color: 'violet', height: 100},
{id: '4', color: 'orange', height: 120},
{id: '5', color: 'coral', height: 100},
{id: '6', color: 'purple', height: 110},
{id: '7', color: 'chocolate', height: 80},
{id: '8', color: 'crimson', height: 90},
{id: '9', color: 'seagreen', height: 90},
];

const Card: React.FC<CardProps> = memo(({id, color, height}) => {
const drag = useReorderableDrag();

return (
<ReorderableListItem>
<Pressable style={[styles.card, {height}]} onLongPress={drag}>
<Text style={[styles.text, {color}]}>Card {id}</Text>
</Pressable>
</ReorderableListItem>
);
});

export const List = () => {
const [data, setData] = useState(list);

const renderItem = ({item}: ListRenderItemInfo<CardProps>) => (
<Card {...item} />
);

const handleReorder = ({from, to}: ReorderableListReorderEvent) => {
const newData = reorderItems(data, from, to);
setData(newData);
};

return (
<ReorderableList
data={data}
onReorder={handleReorder}
renderItem={renderItem}
keyExtractor={item => item.id}
contentContainerStyle={styles.contentContainer}
/>
);
};

const styles = StyleSheet.create({
card: {
justifyContent: 'center',
alignItems: 'center',
margin: 6,
borderRadius: 5,
backgroundColor: 'white',
borderWidth: 1,
borderColor: '#ddd',
},
text: {
fontSize: 20,
},
contentContainer: {
flexGrow: 1,
},
});
22 changes: 22 additions & 0 deletions example/screens/MultipleLists/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import {StyleSheet, View} from 'react-native';

import {List} from './List';

export const MultipleListsScreen = () => (
<View style={styles.container}>
<List />
<View style={styles.separator} />
<List />
</View>
);

const styles = StyleSheet.create({
container: {
flex: 1,
},
separator: {
height: 20,
backgroundColor: 'lightblue',
},
});
94 changes: 94 additions & 0 deletions example/screens/NestedLists/NestedList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React, {memo, useState} from 'react';
import {
ListRenderItemInfo,
Pressable,
StyleSheet,
Text,
View,
} from 'react-native';

import {
NestedReorderableList,
ReorderableListItem,
ReorderableListReorderEvent,
reorderItems,
useReorderableDrag,
} from 'react-native-reorderable-list';

interface CardProps {
id: string;
color: string;
height: number;
}

const list: CardProps[] = [
{id: '0', color: 'red', height: 100},
{id: '1', color: 'blue', height: 150},
{id: '2', color: 'green', height: 80},
{id: '3', color: 'violet', height: 100},
{id: '4', color: 'orange', height: 120},
];

const Card: React.FC<CardProps> = memo(({id, color, height}) => {
const drag = useReorderableDrag();

return (
<ReorderableListItem>
<Pressable style={[styles.card, {height}]} onLongPress={drag}>
<Text style={[styles.text, {color}]}>Card {id}</Text>
</Pressable>
</ReorderableListItem>
);
});

interface NestedListProps {
index: number;
}

export const NestedList: React.FC<NestedListProps> = ({index}) => {
const [data, setData] = useState(list);

const renderItem = ({item}: ListRenderItemInfo<CardProps>) => (
<Card {...item} />
);

const handleReorder = ({from, to}: ReorderableListReorderEvent) => {
const newData = reorderItems(data, from, to);
setData(newData);
};

return (
<NestedReorderableList
data={data}
onReorder={handleReorder}
renderItem={renderItem}
keyExtractor={item => item.id}
scrollEnabled={false}
ListHeaderComponent={
<View style={styles.header}>
<Text style={styles.text}>List {index}</Text>
</View>
}
/>
);
};

const styles = StyleSheet.create({
card: {
justifyContent: 'center',
alignItems: 'center',
margin: 6,
borderRadius: 5,
backgroundColor: 'white',
borderWidth: 1,
borderColor: '#ddd',
},
text: {
fontSize: 20,
},
header: {
height: 40,
justifyContent: 'center',
alignItems: 'center',
},
});
20 changes: 20 additions & 0 deletions example/screens/NestedLists/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import {StyleSheet} from 'react-native';

import {ScrollViewContainer} from 'react-native-reorderable-list';

import {NestedList} from './NestedList';

export const NestedListsScreen = () => (
<ScrollViewContainer style={styles.container}>
<NestedList index={0} />
<NestedList index={1} />
<NestedList index={2} />
</ScrollViewContainer>
);

const styles = StyleSheet.create({
container: {
flex: 1,
},
});
Loading

0 comments on commit 005dd07

Please sign in to comment.