Higher-order components to turn any list into animated, accessible and touch-friendly sortable lists
—
Higher-order component that transforms any React component into a container capable of holding sortable elements. Provides comprehensive configuration for drag behavior, animations, constraints, and event handling.
Creates a sortable container from any React component.
/**
* Higher-order component that makes a component capable of containing sortable elements
* @param wrappedComponent - The React component to enhance with sortable functionality
* @param config - Optional configuration object
* @returns Enhanced React component with sortable container capabilities
*/
function SortableContainer<P>(
wrappedComponent: WrappedComponent<P>,
config?: Config
): React.ComponentClass<P & SortableContainerProps>;
interface Config {
withRef: boolean;
}
interface SortableContainerProps {
// Sorting Direction & Constraints
axis?: Axis;
lockAxis?: Axis;
lockToContainerEdges?: boolean;
lockOffset?: Offset | [Offset, Offset];
// Animation & Visual
helperClass?: string;
transitionDuration?: number;
keyboardSortingTransitionDuration?: number;
hideSortableGhost?: boolean;
// Trigger Behavior
pressDelay?: number;
pressThreshold?: number;
distance?: number;
useDragHandle?: boolean;
// Event Handlers
shouldCancelStart?: (event: SortEvent | SortEventWithTag) => boolean;
updateBeforeSortStart?: SortStartHandler;
onSortStart?: SortStartHandler;
onSortMove?: SortMoveHandler;
onSortEnd?: SortEndHandler;
onSortOver?: SortOverHandler;
// Scrolling & Container
useWindowAsScrollContainer?: boolean;
disableAutoscroll?: boolean;
getContainer?: ContainerGetter;
helperContainer?: HTMLElement | HelperContainerGetter;
getHelperDimensions?: (sort: SortStart) => Dimensions;
// Keyboard Navigation
keyCodes?: KeyCodes;
}
interface KeyCodes {
lift?: number[];
drop?: number[];
cancel?: number[];
up?: number[];
down?: number[];
}Usage Examples:
import React from 'react';
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
// Basic sortable list
const SortableList = SortableContainer(({ items }) => {
return (
<ul>
{items.map((value, index) => (
<SortableItem key={`item-${value}`} index={index} value={value} />
))}
</ul>
);
});
// With configuration options
const AdvancedSortableList = SortableContainer(({ items }) => (
<div className="grid">
{items.map((item, index) => (
<SortableGridItem key={item.id} index={index} item={item} />
))}
</div>
));
// Usage with props
<AdvancedSortableList
items={items}
axis="xy"
helperClass="sortable-helper"
transitionDuration={200}
pressDelay={100}
onSortEnd={handleSortEnd}
useDragHandle={true}
/>/** Items can be sorted horizontally, vertically or in a grid */
axis?: 'x' | 'y' | 'xy'; // default: 'y'
/** Lock movement to an axis while sorting */
lockAxis?: 'x' | 'y';/** CSS class to add to the sortable helper element */
helperClass?: string;
/** Duration of transition when elements shift positions (ms) */
transitionDuration?: number; // default: 300
/** Duration for keyboard sorting transitions (ms) */
keyboardSortingTransitionDuration?: number; // defaults to transitionDuration
/** Whether to auto-hide the element being sorted */
hideSortableGhost?: boolean; // default: true/** Time to wait before sorting begins (ms) - good for mobile */
pressDelay?: number; // default: 0
/** Pixels of movement to tolerate before ignoring press event */
pressThreshold?: number; // default: 5
/** Distance to drag before sorting begins (pixels) */
distance?: number; // default: 0
/** Whether to use drag handles for sorting */
useDragHandle?: boolean; // default: false/** Function to determine if sorting should be cancelled */
shouldCancelStart?: (event: SortEvent | SortEventWithTag) => boolean;
/** Async function called before sorting begins */
updateBeforeSortStart?: SortStartHandler;
/** Callback when sorting begins */
onSortStart?: SortStartHandler;
/** Callback during sorting as cursor moves */
onSortMove?: SortMoveHandler;
/** Callback when moving over an item */
onSortOver?: SortOverHandler;
/** Callback when sorting ends */
onSortEnd?: SortEndHandler;Event Handler Examples:
const handleSortStart = ({ node, index, collection, isKeySorting }, event) => {
console.log('Sorting started:', { index, collection, isKeySorting });
};
const handleSortEnd = ({ oldIndex, newIndex, collection, isKeySorting }, event) => {
if (oldIndex !== newIndex) {
const newItems = arrayMove(items, oldIndex, newIndex);
setItems(newItems);
}
};
const shouldCancel = (event) => {
// Don't sort if clicking on buttons or inputs
return ['INPUT', 'BUTTON', 'SELECT'].includes(event.target.tagName);
};
<SortableList
items={items}
onSortStart={handleSortStart}
onSortEnd={handleSortEnd}
shouldCancelStart={shouldCancel}
/>/** Use window as the scrolling container */
useWindowAsScrollContainer?: boolean; // default: false
/** Disable autoscrolling while dragging */
disableAutoscroll?: boolean; // default: false
/** Function to return the scrollable container element */
getContainer?: ContainerGetter;
/** Container for the sortable helper element */
helperContainer?: HTMLElement | HelperContainerGetter; // default: document.body
/** Function to compute helper dimensions */
getHelperDimensions?: (sort: SortStart) => Dimensions;/** Lock movement to container edges */
lockToContainerEdges?: boolean; // default: false
/** Offset distance from container edges when locked */
lockOffset?: Offset | [Offset, Offset]; // default: '50%'Constraint Examples:
// Lock to container with 10px margins
<SortableList
items={items}
lockToContainerEdges={true}
lockOffset="10px"
onSortEnd={handleSortEnd}
/>
// Different lock offsets for top/bottom
<SortableList
items={items}
lockToContainerEdges={true}
lockOffset={["0%", "100%"]}
onSortEnd={handleSortEnd}
/>/** Keyboard navigation key codes */
keyCodes?: {
lift?: number[]; // default: [32] (SPACE)
drop?: number[]; // default: [32] (SPACE)
cancel?: number[]; // default: [27] (ESC)
up?: number[]; // default: [38, 37] (UP, LEFT)
down?: number[]; // default: [40, 39] (DOWN, RIGHT)
};Keyboard Navigation Example:
// Custom key bindings
<SortableList
items={items}
keyCodes={{
lift: [13, 32], // Enter or Space to lift
drop: [13, 32], // Enter or Space to drop
cancel: [27], // Escape to cancel
up: [38, 87], // Up arrow or W
down: [40, 83], // Down arrow or S
}}
onSortEnd={handleSortEnd}
/>interface Config {
/** Enable access to wrapped component instance */
withRef: boolean;
}withRef Example:
const SortableList = SortableContainer(ListComponent, { withRef: true });
// Access wrapped instance
const listRef = useRef();
const wrappedInstance = listRef.current?.getWrappedInstance();
<SortableList ref={listRef} items={items} onSortEnd={handleSortEnd} />React context that provides access to the internal sortable manager. Primarily used for advanced integrations and custom components.
/**
* React context providing access to sortable manager
*/
const SortableContext: React.Context<{
manager: Manager;
}>;Usage Examples:
import React, { useContext } from 'react';
import { SortableContext } from 'react-sortable-hoc';
// Access sortable manager from context
const CustomSortableComponent = () => {
const { manager } = useContext(SortableContext);
// Use manager for advanced operations
const handleCustomAction = () => {
console.log('Sortable elements:', manager.refs);
};
return (
<div onClick={handleCustomAction}>
Custom sortable component
</div>
);
};
// Use within SortableContainer
const SortableList = SortableContainer(({ items }) => (
<ul>
{items.map((value, index) => (
<SortableItem key={`item-${index}`} index={index} value={value} />
))}
<CustomSortableComponent />
</ul>
));Note: SortableContext is an advanced API primarily intended for library authors and complex integrations. Most applications should use the standard SortableContainer, SortableElement, and SortableHandle components.
Install with Tessl CLI
npx tessl i tessl/npm-react-sortable-hoc