Skip to content

Commit 56f0ef3

Browse files
authored
feat: Base multi zone provider (#409)
## Description This PR adds a basic implementation of the multi zone provider. It just adds the base component without any functionality and the outlet component that detects if the active item is currently hovered over it. Upcoming PRs will add more functionalities, like dragging items between different sortable containers, etc. ## Example recording https://github.com/user-attachments/assets/02fd6bcc-7cc4-4775-b70e-f8f7e0b47892
1 parent 6994c7a commit 56f0ef3

36 files changed

Lines changed: 420 additions & 207 deletions
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { useCallback } from 'react';
2+
import type { SortableGridRenderItem } from 'react-native-sortables';
3+
import Sortable from 'react-native-sortables';
4+
5+
import { GridCard, ScrollScreen, Section } from '@/components';
6+
import { spacing } from '@/theme';
7+
import { getItems } from '@/utils';
8+
9+
const DATA = getItems(8);
10+
const COLUMNS = 4;
11+
12+
export default function MultiZoneExample() {
13+
const renderItem = useCallback<SortableGridRenderItem<string>>(
14+
({ item }) => <GridCard>{item}</GridCard>,
15+
[]
16+
);
17+
18+
return (
19+
<ScrollScreen includeNavBarHeight>
20+
<Sortable.MultiZoneProvider minActivationDistance={10}>
21+
<Section title='Section 1'>
22+
<Sortable.Grid
23+
columnGap={spacing.xs}
24+
columns={COLUMNS}
25+
data={DATA}
26+
renderItem={renderItem}
27+
rowGap={spacing.xs}
28+
debug
29+
/>
30+
</Section>
31+
32+
<Section title='Section 2'>
33+
<Sortable.Grid
34+
columnGap={spacing.xs}
35+
columns={COLUMNS}
36+
data={DATA}
37+
renderItem={renderItem}
38+
rowGap={spacing.xs}
39+
debug
40+
/>
41+
</Section>
42+
</Sortable.MultiZoneProvider>
43+
</ScrollScreen>
44+
);
45+
}

example/app/src/examples/SortableGrid/features/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ export { default as DragHandleExample } from './DragHandleExample';
77
export { default as DropIndicatorExample } from './DropIndicatorExample';
88
export { default as FixedItemsExample } from './FixedItemsExample';
99
export { default as HorizontalAutoScrollExample } from './HorizontalAutoScrollExample';
10+
export { default as MultiZoneExample } from './MultiZoneExample';
1011
export { default as OrderingStrategyExample } from './OrderingStrategyExample';
1112
export { default as TouchableExample } from './TouchableExample';

example/app/src/examples/navigation/routes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ const routes: Routes = {
5151
Component: SortableGrid.features.FixedItemsExample,
5252
name: 'Fixed Items'
5353
},
54+
MultiZone: {
55+
Component: SortableGrid.features.MultiZoneExample,
56+
name: 'Multi Zone'
57+
},
5458
Callbacks: {
5559
Component: SortableGrid.features.CallbacksExample,
5660
name: 'Callbacks'

packages/react-native-sortables/CHANGELOG.md

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,29 @@
11
## [1.7.1](https://github.com/MatiPl01/react-native-sortables/compare/v1.7.0...v1.7.1) (2025-06-16)
22

3-
43
### Bug Fixes
54

6-
* Flex layout issues ([#400](https://github.com/MatiPl01/react-native-sortables/issues/400)) ([891c0b3](https://github.com/MatiPl01/react-native-sortables/commit/891c0b385ed779b7548cdd8da029e09ea8cde61a))
7-
* Flex layout with custom alignmnets on web ([#401](https://github.com/MatiPl01/react-native-sortables/issues/401)) ([a74cb4b](https://github.com/MatiPl01/react-native-sortables/commit/a74cb4b313bc60125b27d62e39567940dccaf52d))
8-
* Invalid items animation on screen change ([#396](https://github.com/MatiPl01/react-native-sortables/issues/396)) ([3262064](https://github.com/MatiPl01/react-native-sortables/commit/3262064207d63ca3a251b15df685f6ffe358a4d2))
9-
* Items not animated before `sortEnabled` is set to `true` ([#398](https://github.com/MatiPl01/react-native-sortables/issues/398)) ([5ff360c](https://github.com/MatiPl01/react-native-sortables/commit/5ff360c765fcbf72feef260eb779e1e1456c0216))
10-
* Multi touch flickering issue ([#392](https://github.com/MatiPl01/react-native-sortables/issues/392)) ([6427c8d](https://github.com/MatiPl01/react-native-sortables/commit/6427c8dc370f20362facf945fa34af2e5a5c4585))
5+
- Flex layout issues ([#400](https://github.com/MatiPl01/react-native-sortables/issues/400)) ([891c0b3](https://github.com/MatiPl01/react-native-sortables/commit/891c0b385ed779b7548cdd8da029e09ea8cde61a))
6+
- Flex layout with custom alignmnets on web ([#401](https://github.com/MatiPl01/react-native-sortables/issues/401)) ([a74cb4b](https://github.com/MatiPl01/react-native-sortables/commit/a74cb4b313bc60125b27d62e39567940dccaf52d))
7+
- Invalid items animation on screen change ([#396](https://github.com/MatiPl01/react-native-sortables/issues/396)) ([3262064](https://github.com/MatiPl01/react-native-sortables/commit/3262064207d63ca3a251b15df685f6ffe358a4d2))
8+
- Items not animated before `sortEnabled` is set to `true` ([#398](https://github.com/MatiPl01/react-native-sortables/issues/398)) ([5ff360c](https://github.com/MatiPl01/react-native-sortables/commit/5ff360c765fcbf72feef260eb779e1e1456c0216))
9+
- Multi touch flickering issue ([#392](https://github.com/MatiPl01/react-native-sortables/issues/392)) ([6427c8d](https://github.com/MatiPl01/react-native-sortables/commit/6427c8dc370f20362facf945fa34af2e5a5c4585))
1110

1211
# [1.7.0](https://github.com/MatiPl01/react-native-sortables/compare/v1.6.0...v1.7.0) (2025-05-26)
1312

14-
1513
### Bug Fixes
1614

17-
* Invalid call to gesture manager when item is no longer available ([#385](https://github.com/MatiPl01/react-native-sortables/issues/385)) ([5e5e1ca](https://github.com/MatiPl01/react-native-sortables/commit/5e5e1ca9cb7e93d3b4707e185c9bb30d0afd6de7))
18-
* Invalid custom handle measurement ([#384](https://github.com/MatiPl01/react-native-sortables/issues/384)) ([ad03d2b](https://github.com/MatiPl01/react-native-sortables/commit/ad03d2b7e970f57583e15e6cce9b3424b1ec220b)), closes [#377](https://github.com/MatiPl01/react-native-sortables/issues/377)
19-
* onPress not fired when activation delay is low ([#377](https://github.com/MatiPl01/react-native-sortables/issues/377)) ([07065b5](https://github.com/MatiPl01/react-native-sortables/commit/07065b527f23637dfd3dd96401490dc0b89e4f96)), closes [#375](https://github.com/MatiPl01/react-native-sortables/issues/375)
20-
* Order change callback invalid keyToIndex and shadow color interpolation ([#380](https://github.com/MatiPl01/react-native-sortables/issues/380)) ([c0b3c03](https://github.com/MatiPl01/react-native-sortables/commit/c0b3c0351f6837f9b9688dfc387247be10b145bc))
21-
* Stop passing excessive data to the item context ([#383](https://github.com/MatiPl01/react-native-sortables/issues/383)) ([0b466ac](https://github.com/MatiPl01/react-native-sortables/commit/0b466aca7434327e4acd81f941b4f5dd80e1c02d))
22-
15+
- Invalid call to gesture manager when item is no longer available ([#385](https://github.com/MatiPl01/react-native-sortables/issues/385)) ([5e5e1ca](https://github.com/MatiPl01/react-native-sortables/commit/5e5e1ca9cb7e93d3b4707e185c9bb30d0afd6de7))
16+
- Invalid custom handle measurement ([#384](https://github.com/MatiPl01/react-native-sortables/issues/384)) ([ad03d2b](https://github.com/MatiPl01/react-native-sortables/commit/ad03d2b7e970f57583e15e6cce9b3424b1ec220b)), closes [#377](https://github.com/MatiPl01/react-native-sortables/issues/377)
17+
- onPress not fired when activation delay is low ([#377](https://github.com/MatiPl01/react-native-sortables/issues/377)) ([07065b5](https://github.com/MatiPl01/react-native-sortables/commit/07065b527f23637dfd3dd96401490dc0b89e4f96)), closes [#375](https://github.com/MatiPl01/react-native-sortables/issues/375)
18+
- Order change callback invalid keyToIndex and shadow color interpolation ([#380](https://github.com/MatiPl01/react-native-sortables/issues/380)) ([c0b3c03](https://github.com/MatiPl01/react-native-sortables/commit/c0b3c0351f6837f9b9688dfc387247be10b145bc))
19+
- Stop passing excessive data to the item context ([#383](https://github.com/MatiPl01/react-native-sortables/issues/383)) ([0b466ac](https://github.com/MatiPl01/react-native-sortables/commit/0b466aca7434327e4acd81f941b4f5dd80e1c02d))
2320

2421
### Features
2522

26-
* Active item dropped callback, more props in drag start callback ([#381](https://github.com/MatiPl01/react-native-sortables/issues/381)) ([ef6e6cd](https://github.com/MatiPl01/react-native-sortables/commit/ef6e6cd84e1df65b7690c57d6fa8af13160b77aa))
27-
* Add keyToIndex and indexToKey to the item context ([#379](https://github.com/MatiPl01/react-native-sortables/issues/379)) ([9166043](https://github.com/MatiPl01/react-native-sortables/commit/91660436ace23d988c2ab83fb51d8b932cd3bffe))
28-
* Add more params to the item drop callback ([#382](https://github.com/MatiPl01/react-native-sortables/issues/382)) ([36fe591](https://github.com/MatiPl01/react-native-sortables/commit/36fe59171e9fb58d3a1e6d63f7fd0000ffd4cf38))
29-
* Add more touch events to the touchable ([#378](https://github.com/MatiPl01/react-native-sortables/issues/378)) ([c60500f](https://github.com/MatiPl01/react-native-sortables/commit/c60500fb0c3be27325c8b91a6f42c9237cf72d6f))
23+
- Active item dropped callback, more props in drag start callback ([#381](https://github.com/MatiPl01/react-native-sortables/issues/381)) ([ef6e6cd](https://github.com/MatiPl01/react-native-sortables/commit/ef6e6cd84e1df65b7690c57d6fa8af13160b77aa))
24+
- Add keyToIndex and indexToKey to the item context ([#379](https://github.com/MatiPl01/react-native-sortables/issues/379)) ([9166043](https://github.com/MatiPl01/react-native-sortables/commit/91660436ace23d988c2ab83fb51d8b932cd3bffe))
25+
- Add more params to the item drop callback ([#382](https://github.com/MatiPl01/react-native-sortables/issues/382)) ([36fe591](https://github.com/MatiPl01/react-native-sortables/commit/36fe59171e9fb58d3a1e6d63f7fd0000ffd4cf38))
26+
- Add more touch events to the touchable ([#378](https://github.com/MatiPl01/react-native-sortables/issues/378)) ([c60500f](https://github.com/MatiPl01/react-native-sortables/commit/c60500fb0c3be27325c8b91a6f42c9237cf72d6f))
3027

3128
# [1.6.0](https://github.com/MatiPl01/react-native-sortables/compare/v1.5.2...v1.6.0) (2025-04-27)
3229

packages/react-native-sortables/src/components/SortableFlex.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ function SortableFlex(props: SortableFlexProps) {
7070
{...sharedProps}
7171
controlledContainerDimensions={controlledContainerDimensions}
7272
debug={debug}
73-
initialItemsStyleOverride={styles.styleOverride}
7473
itemKeys={itemKeys}
74+
initialCanMeasureItems
7575
onDragEnd={onDragEnd}>
7676
<FlexLayoutProvider {...styleProps} itemsCount={itemKeys.length}>
7777
<OrderUpdaterComponent
@@ -176,7 +176,8 @@ function SortableFlexInner({
176176
entering={itemEntering ?? undefined}
177177
exiting={itemExiting ?? undefined}
178178
itemKey={key}
179-
key={key}>
179+
key={key}
180+
style={styles.styleOverride}>
180181
{child}
181182
</DraggableView>
182183
))}

packages/react-native-sortables/src/components/SortableGrid.tsx

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,6 @@ function SortableGrid<I>(props: SortableGridProps<I>) {
8989
controlledContainerDimensions={controlledContainerDimensions}
9090
debug={debug}
9191
itemKeys={itemKeys}
92-
initialItemsStyleOverride={
93-
isVertical ? undefined : styles.horizontalStyleOverride
94-
}
9592
onDragEnd={onDragEnd}>
9693
<GridLayoutProvider
9794
columnGap={columnGapValue}
@@ -165,18 +162,40 @@ function SortableGridInner<I>({
165162
rowHeight,
166163
...containerProps
167164
}: SortableGridInnerProps<I>) {
165+
const { mainGroupSize } = useGridLayoutContext();
166+
168167
const animatedInnerStyle = useAnimatedStyle(() => ({
169168
flexDirection: isVertical ? 'row' : 'column',
170169
height: isVertical ? undefined : groups * (rowHeight + rowGap.value),
171-
marginHorizontal: -columnGap.value / 2,
172-
marginVertical: -rowGap.value / 2
170+
...(mainGroupSize.value
171+
? {
172+
columnGap: columnGap.value,
173+
marginHorizontal: 0,
174+
marginVertical: 0,
175+
rowGap: rowGap.value
176+
}
177+
: {
178+
marginHorizontal: -columnGap.value / 2,
179+
marginVertical: -rowGap.value / 2
180+
})
173181
}));
174182

175-
const animatedItemStyle = useAnimatedStyle(() => ({
176-
flexBasis: `${100 / groups}%`,
177-
paddingHorizontal: columnGap.value / 2,
178-
paddingVertical: rowGap.value / 2
179-
}));
183+
const animatedItemStyle = useAnimatedStyle(() => {
184+
if (!mainGroupSize.value) {
185+
return {
186+
flexBasis: `${100 / groups}%`,
187+
paddingHorizontal: columnGap.value / 2,
188+
paddingVertical: rowGap.value / 2
189+
};
190+
}
191+
192+
return {
193+
flexBasis: 'auto',
194+
[isVertical ? 'width' : 'height']: mainGroupSize.value,
195+
paddingHorizontal: 0,
196+
paddingVertical: 0
197+
};
198+
});
180199

181200
return (
182201
<SortableContainer
@@ -191,7 +210,10 @@ function SortableGridInner<I>({
191210
itemKey={key}
192211
key={key}
193212
renderItem={renderItem}
194-
style={animatedItemStyle}
213+
style={[
214+
animatedItemStyle,
215+
!isVertical && styles.horizontalStyleOverride
216+
]}
195217
/>
196218
))}
197219
</SortableContainer>

packages/react-native-sortables/src/components/shared/CustomHandle.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type PropsWithChildren, useCallback, useEffect } from 'react';
22
import { View } from 'react-native';
33
import { GestureDetector } from 'react-native-gesture-handler';
4-
import { useAnimatedRef } from 'react-native-reanimated';
4+
import { runOnUI, useAnimatedRef } from 'react-native-reanimated';
55

66
import {
77
useCustomHandleContext,
@@ -67,7 +67,7 @@ function CustomHandleComponent({
6767

6868
return (
6969
<GestureDetector gesture={gesture.enabled(dragEnabled)} userSelect='none'>
70-
<View collapsable={false} ref={handleRef} onLayout={onLayout}>
70+
<View collapsable={false} ref={handleRef} onLayout={runOnUI(onLayout)}>
7171
{children}
7272
</View>
7373
</GestureDetector>

packages/react-native-sortables/src/components/shared/DraggableView/DraggableView.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,9 @@ function DraggableView({
4040
const commonValuesContext = useCommonValuesContext();
4141
const { handleItemMeasurement, removeItemMeasurements } =
4242
useMeasurementsContext();
43-
const { activeItemKey, componentId, customHandle, itemsOverridesStyle } =
44-
commonValuesContext;
43+
const { activeItemKey, containerId, customHandle } = commonValuesContext;
4544

46-
const teleportedItemId = `${componentId}-${key}`;
45+
const teleportedItemId = `${containerId}-${key}`;
4746

4847
const [isTeleported, setIsTeleported] = useState(false);
4948
const activationAnimationProgress = useMutableValue(0);
@@ -53,7 +52,7 @@ function DraggableView({
5352

5453
useEffect(() => {
5554
return () => removeItemMeasurements(key);
56-
}, [key, removeItemMeasurements, teleportedItemId]);
55+
}, [key, removeItemMeasurements]);
5756

5857
useEffect(() => {
5958
if (!portalContext) {
@@ -90,7 +89,6 @@ function DraggableView({
9089
{...layoutAnimations}
9190
cellStyle={[style, itemStyles]}
9291
hidden={hidden}
93-
itemsOverridesStyle={itemsOverridesStyle}
9492
onMeasure={onMeasure}>
9593
<LayoutAnimationConfig skipEntering={false} skipExiting={false}>
9694
{children}
@@ -127,7 +125,6 @@ function DraggableView({
127125
baseCellStyle={style}
128126
isActive={isActive}
129127
itemKey={key}
130-
itemsOverridesStyle={itemsOverridesStyle}
131128
onMeasure={onMeasure}>
132129
{children}
133130
</TeleportedItemCell>

packages/react-native-sortables/src/components/shared/DraggableView/ItemCell.tsx

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
StyleSheet,
66
type ViewStyle
77
} from 'react-native';
8-
import type { AnimatedStyle } from 'react-native-reanimated';
98
import Animated from 'react-native-reanimated';
109

1110
import type {
@@ -16,7 +15,6 @@ import type {
1615
import AnimatedOnLayoutView from '../AnimatedOnLayoutView';
1716

1817
export type ItemCellProps = PropsWithChildren<{
19-
itemsOverridesStyle: AnimatedStyle<ViewStyle>;
2018
cellStyle: AnimatedStyleProp;
2119
onMeasure: MeasureCallback;
2220
hidden?: boolean;
@@ -30,16 +28,8 @@ export default function ItemCell({
3028
entering,
3129
exiting,
3230
hidden,
33-
itemsOverridesStyle,
3431
onMeasure
3532
}: ItemCellProps) {
36-
const style = [
37-
styles.decoration,
38-
cellStyle,
39-
itemsOverridesStyle,
40-
hidden && styles.hidden
41-
];
42-
4333
const onLayout = hidden
4434
? undefined
4535
: ({
@@ -51,7 +41,9 @@ export default function ItemCell({
5141
};
5242

5343
return (
54-
<AnimatedOnLayoutView style={style} onLayout={onLayout}>
44+
<AnimatedOnLayoutView
45+
style={[styles.decoration, cellStyle, hidden && styles.hidden]}
46+
onLayout={onLayout}>
5547
{/* TODO - remove itemEntering and itemExiting layout animation in sortables v2 */}
5648
{entering || exiting ? (
5749
<Animated.View entering={entering} exiting={exiting}>

packages/react-native-sortables/src/components/shared/DraggableView/TeleportedItemCell.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import type { PropsWithChildren } from 'react';
2-
import type { ViewStyle } from 'react-native';
3-
import type { AnimatedStyle, SharedValue } from 'react-native-reanimated';
2+
import type { SharedValue } from 'react-native-reanimated';
43
import { LayoutAnimationConfig } from 'react-native-reanimated';
54

65
import { useTeleportedItemStyles } from '../../../providers';
76
import type { AnimatedStyleProp, MeasureCallback } from '../../../types';
87
import ItemCell from './ItemCell';
98

109
type TeleportedItemCellProps = PropsWithChildren<{
11-
itemsOverridesStyle: AnimatedStyle<ViewStyle>;
1210
activationAnimationProgress: SharedValue<number>;
1311
baseCellStyle: AnimatedStyleProp;
1412
isActive: SharedValue<boolean>;
@@ -22,7 +20,6 @@ export default function TeleportedItemCell({
2220
children,
2321
isActive,
2422
itemKey,
25-
itemsOverridesStyle,
2623
onMeasure
2724
}: TeleportedItemCellProps) {
2825
const teleportedItemStyles = useTeleportedItemStyles(
@@ -34,7 +31,6 @@ export default function TeleportedItemCell({
3431
return (
3532
<ItemCell
3633
cellStyle={[baseCellStyle, teleportedItemStyles]}
37-
itemsOverridesStyle={itemsOverridesStyle}
3834
onMeasure={onMeasure}>
3935
<LayoutAnimationConfig skipEntering>{children}</LayoutAnimationConfig>
4036
</ItemCell>

0 commit comments

Comments
 (0)