Skip to content

Commit 45b2eec

Browse files
authored
docs: Active item portal provider (#366)
## Description Add active item portal provider example and `PortalProvider` documentation pages.
1 parent 3dd7b38 commit 45b2eec

8 files changed

Lines changed: 225 additions & 9 deletions

File tree

packages/docs/docs/customization/_category_.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"label": "Customization",
3-
"position": 6,
3+
"position": 8,
44
"link": {
55
"type": "generated-index"
66
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
---
2+
sidebar_position: 8
3+
description: ''
4+
---
5+
6+
# Active Item Portal
7+
8+
## Description
9+
10+
This example demonstrates how to use the active item portal to render the active item on top of other components.
11+
12+
In this case, the `PortalProvider` is used to render the active item outside of the `ScrollView` content bounds. This is the only way to render only the active item without cropping it whilst keeping the rest of the grid within the `ScrollView` content bounds.
13+
14+
## Source Code
15+
16+
```tsx
17+
import { useCallback, useState } from 'react';
18+
import { Pressable, StyleSheet, Text, View } from 'react-native';
19+
import Animated, { useAnimatedRef } from 'react-native-reanimated';
20+
import type { SortableGridRenderItem } from 'react-native-sortables';
21+
import Sortable from 'react-native-sortables';
22+
23+
const DATA = Array.from({ length: 18 }, (_, index) => `Item ${index + 1}`);
24+
25+
export default function Example() {
26+
const [portalEnabled, setPortalEnabled] = useState(false);
27+
const scrollableRef = useAnimatedRef<Animated.ScrollView>();
28+
29+
const renderItem = useCallback<SortableGridRenderItem<string>>(
30+
({ item }) => (
31+
<View style={styles.card}>
32+
<Text style={styles.text}>{item}</Text>
33+
</View>
34+
),
35+
[]
36+
);
37+
38+
return (
39+
<View style={styles.container}>
40+
{/* highlight-start */}
41+
<Sortable.PortalProvider enabled={portalEnabled}>
42+
{/* highlight-end */}
43+
<View style={styles.gridContainer}>
44+
<Animated.ScrollView
45+
contentContainerStyle={styles.contentContainer}
46+
ref={scrollableRef}>
47+
<Sortable.Grid
48+
columnGap={10}
49+
columns={3}
50+
data={DATA}
51+
renderItem={renderItem}
52+
rowGap={10}
53+
scrollableRef={scrollableRef}
54+
/>
55+
</Animated.ScrollView>
56+
</View>
57+
<Pressable onPress={() => setPortalEnabled(prev => !prev)}>
58+
<Text
59+
style={
60+
styles.buttonText
61+
}>{`${portalEnabled ? 'Disable' : 'Enable'} Portal`}</Text>
62+
</Pressable>
63+
{/* highlight-start */}
64+
</Sortable.PortalProvider>
65+
{/* highlight-end */}
66+
</View>
67+
);
68+
}
69+
70+
const styles = StyleSheet.create({
71+
container: {
72+
flex: 1,
73+
alignItems: 'center',
74+
justifyContent: 'center',
75+
backgroundColor: '#DDE2E3'
76+
},
77+
gridContainer: {
78+
margin: 15,
79+
borderRadius: 10,
80+
height: 400,
81+
backgroundColor: '#FFFFFF'
82+
},
83+
card: {
84+
alignItems: 'center',
85+
backgroundColor: '#36877F',
86+
borderRadius: 10,
87+
height: 100,
88+
justifyContent: 'center'
89+
},
90+
contentContainer: {
91+
padding: 10
92+
},
93+
text: {
94+
color: 'white',
95+
fontWeight: 'bold'
96+
},
97+
buttonText: {
98+
color: '#36877F',
99+
fontSize: 18,
100+
fontWeight: 'bold'
101+
}
102+
});
103+
```
104+
105+
## Result
106+
107+
import video from '@site/static/video/grid-active-item-portal.mp4';
108+
109+
<video autoPlay loop muted width='300px' src={video} />

packages/docs/docs/helper-components/handle.mdx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,17 @@ Determines the reordering behavior of the item.
4545
- `'non-draggable'` - the item **cannot be dragged** but **moves** when other items are reordered.
4646
- `'fixed'` - the item **cannot be dragged** and **does not move** when other items are reordered.
4747

48-
:::info Complete Usage Examples
49-
50-
- Complete usage example: [Custom Handle](/grid/examples/custom-handle)
51-
- Grid with fixed items: [Fixed Items](/grid/examples/fixed-items)
52-
53-
:::
54-
5548
### children
5649

5750
The handle component (can be anything, e.g. an icon, image, etc.).
5851

5952
| type | default | required |
6053
| ----------- | ------- | -------- |
6154
| `ReactNode` | NO | NO |
55+
56+
:::info
57+
58+
- Complete usage example: [Custom Handle](/grid/examples/custom-handle)
59+
- Grid with fixed items: [Fixed Items](/grid/examples/fixed-items)
60+
61+
:::
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"label": "Feature Providers",
3+
"position": 7,
4+
"link": {
5+
"type": "generated-index"
6+
}
7+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
---
2+
sidebar_position: 1
3+
description: 'Allows rendering the active item on top of other components'
4+
---
5+
6+
# Portal Provider
7+
8+
## Overview
9+
10+
The **Portal Provider** component allows you to render the active item on top of other components.
11+
12+
It is useful if your sortable component is **deeply nested within other components** and you want to render the active item on top of all of them or if you want to render the active item **outside of the `ScrollView` content bounds**.
13+
14+
## Usage
15+
16+
Just wrap your sortable component (and any other components you want the active item to render above) with the `Sortable.PortalProvider` component.
17+
18+
```tsx
19+
import Sortable from 'react-native-sortables';
20+
21+
// ... other components
22+
<Sortable.PortalProvider>
23+
{/* other components and the nested sortable component */}
24+
</Sortable.PortalProvider>;
25+
// ... other components
26+
```
27+
28+
## Example
29+
30+
import video from '@site/static/video/portal-provider.mp4';
31+
32+
<video autoPlay loop muted width='300px' src={video} />
33+
34+
## Props
35+
36+
### enabled
37+
38+
Determines whether the portal functionality is enabled.
39+
40+
| type | default | required |
41+
| --------- | ------- | -------- |
42+
| `boolean` | `true` | NO |
43+
44+
When enabled, the active item will be rendered in a portal outlet, which is rendered on top of other components rendered within the `PortalProvider` component.
45+
46+
### children
47+
48+
The components above which the active item should be rendered.
49+
50+
| type | default | required |
51+
| ----------- | ------- | -------- |
52+
| `ReactNode` | NO | YES |
53+
54+
## How It Works?
55+
56+
The Portal Provider creates a separate portal outlet component higher in the view hierarchy. When an item becomes active, it is teleported to this outlet, allowing it to be rendered on top of other components. The provider synchronizes the position of the teleported item with the original item and manages the visibility of the original item while it is being dragged.
57+
58+
This approach ensures a smooth transition between the item rendered within the sortable component and the one rendered in the portal.
59+
60+
## Remarks
61+
62+
### Active Item Copy
63+
64+
The portal renders the active item on a different layer, within a different parent component than the item is normally rendered in. As a result, the copy of the active item is mounted (not re-rendered) while the old component within the sortable container is still rendered but invisible.
65+
66+
This means there are 2 instances of the active item component rendered at the same time until it is dropped.
67+
68+
### Component State
69+
70+
Since the teleported component is a separate instance from the original one, any local state (like `useState` or fetched data) will not be shared between the two instances.
71+
72+
For components that need to maintain state during dragging, consider using:
73+
74+
- Context providers to share state
75+
- External store or state management library
76+
- Lifting state up to a parent component (but it may lead to performance issues if not implemented correctly)
77+
78+
### Layout Animations
79+
80+
Since the view is mounted and unmounted within the portal outlet, all entering and exiting animations used in this view triggered on render will be triggered here as well.
81+
82+
### Paper (Old Architecture) Only
83+
84+
When using the Portal Provider with in the React Native Old Architecture, you may encounter a shadow-related warning saying that the shadow calculation is inefficient. To resolve this, either set `activeItemShadowOpacity={0}` to remove the shadow or ignore the warning as the shadow only affects a single (active) view.
85+
86+
:::warning
87+
88+
The Portal Provider uses complex synchronization logic to synchronize Reanimated style updates with React renders. While this approach works well in most cases, it may have some limitations.
89+
90+
If you encounter unexpected behavior or any kind of issues, please report them on [GitHub](https://github.com/matipl01/react-native-sortables/issues) with a detailed description of the problem.
91+
92+
:::
93+
94+
:::info
95+
96+
For the complete usage example of the Portal Provider, check out the [Active Item Portal](/grid/examples/active-item-portal) example.
97+
98+
:::

packages/docs/src/css/custom.css

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ button {
3636
--ifm-color-primary-lighter: #359962;
3737
--ifm-color-primary-lightest: #3cad6e;
3838
--ifm-code-font-size: 95%;
39-
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
4039
--ifm-details-color-background: #edf7f3;
4140
--ifm-details-color-border: #33925d40;
4241
--ifm-details-color-text: var(--ifm-color-emphasis-900);
4342
--ifm-details-color-icon: var(--ifm-color-emphasis-900);
43+
--ifm-tabs-color-active: var(--ifm-color-emphasis-900);
44+
--docusaurus-highlighted-code-line-bg: #c6e1d6;
4445
}
4546

4647
/* For readability concerns, you should choose a lighter palette in dark mode. */
@@ -57,6 +58,7 @@ button {
5758
--ifm-details-color-border: #25c2a040;
5859
--ifm-details-color-text: #ffffff;
5960
--ifm-details-color-icon: #25c2a0;
61+
--docusaurus-highlighted-code-line-bg: #25c2a020;
6062
}
6163

6264
hr {
1.42 MB
Binary file not shown.
570 KB
Binary file not shown.

0 commit comments

Comments
 (0)