or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

array-utilities.mdindex.mdsortable-container.mdsortable-element.mdsortable-handle.md

sortable-element.mddocs/

0

# Sortable Element

1

2

Higher-order component that makes individual React components sortable within a SortableContainer. Provides simple interface for element positioning, grouping, and state management.

3

4

## Capabilities

5

6

### SortableElement HOC

7

8

Makes any React component sortable within a SortableContainer.

9

10

```typescript { .api }

11

/**

12

* Higher-order component that makes an element sortable within a SortableContainer

13

* @param wrappedComponent - The React component to enhance with sortable functionality

14

* @param config - Optional configuration object

15

* @returns Enhanced React component with sortable element capabilities

16

*/

17

function SortableElement<P>(

18

wrappedComponent: WrappedComponent<P>,

19

config?: Config

20

): React.ComponentClass<P & SortableElementProps>;

21

22

interface SortableElementProps {

23

/** Element's sort index within its collection (required) */

24

index: number;

25

/** Collection identifier for grouping elements */

26

collection?: Offset;

27

/** Whether the element should be sortable */

28

disabled?: boolean;

29

}

30

31

interface Config {

32

withRef: boolean;

33

}

34

```

35

36

**Usage Examples:**

37

38

```typescript

39

import React from 'react';

40

import { SortableElement } from 'react-sortable-hoc';

41

42

// Basic sortable item

43

const SortableItem = SortableElement(({ value }) => <li>{value}</li>);

44

45

// More complex sortable component

46

const SortableCard = SortableElement(({ title, description, onEdit }) => (

47

<div className="card">

48

<h3>{title}</h3>

49

<p>{description}</p>

50

<button onClick={onEdit}>Edit</button>

51

</div>

52

));

53

54

// Usage in container

55

const items = ['Item 1', 'Item 2', 'Item 3'];

56

{items.map((value, index) => (

57

<SortableItem

58

key={`item-${index}`}

59

index={index}

60

value={value}

61

/>

62

))}

63

```

64

65

### Required Props

66

67

#### Index Property

68

69

```typescript { .api }

70

/** Element's sortable index within its collection (required) */

71

index: number;

72

```

73

74

The `index` prop is required and determines the element's position in the sortable sequence. It must be unique within the collection.

75

76

**Index Examples:**

77

78

```typescript

79

// Basic sequential indexing

80

{items.map((item, index) => (

81

<SortableItem key={item.id} index={index} value={item.value} />

82

))}

83

84

// Custom indexing for sparse arrays

85

const sparseItems = [

86

{ id: 'a', value: 'First', sortOrder: 0 },

87

{ id: 'b', value: 'Second', sortOrder: 2 },

88

{ id: 'c', value: 'Third', sortOrder: 5 },

89

];

90

91

{sparseItems.map((item) => (

92

<SortableItem

93

key={item.id}

94

index={item.sortOrder}

95

value={item.value}

96

/>

97

))}

98

```

99

100

### Optional Props

101

102

#### Collection Property

103

104

```typescript { .api }

105

/** Collection identifier for grouping elements */

106

collection?: number | string; // default: 0

107

```

108

109

The `collection` prop groups elements into separate sortable areas within the same container. Useful for multi-list scenarios or categorized sorting.

110

111

**Collection Examples:**

112

113

```typescript

114

// Multiple lists in same container

115

const todoItems = [

116

{ id: 1, text: 'Task 1', status: 'pending' },

117

{ id: 2, text: 'Task 2', status: 'completed' },

118

{ id: 3, text: 'Task 3', status: 'pending' },

119

];

120

121

{todoItems.map((item, index) => (

122

<SortableTask

123

key={item.id}

124

index={index}

125

collection={item.status} // Group by status

126

task={item}

127

/>

128

))}

129

130

// Numeric collections

131

{leftColumnItems.map((item, index) => (

132

<SortableItem key={item.id} index={index} collection={0} value={item} />

133

))}

134

{rightColumnItems.map((item, index) => (

135

<SortableItem key={item.id} index={index} collection={1} value={item} />

136

))}

137

```

138

139

#### Disabled Property

140

141

```typescript { .api }

142

/** Whether the element should be sortable */

143

disabled?: boolean; // default: false

144

```

145

146

When `disabled` is true, the element cannot be dragged or participate in sorting operations.

147

148

**Disabled Examples:**

149

150

```typescript

151

// Conditionally disable elements

152

{items.map((item, index) => (

153

<SortableItem

154

key={item.id}

155

index={index}

156

value={item.value}

157

disabled={item.locked || item.readonly}

158

/>

159

))}

160

161

// Disable based on user permissions

162

{items.map((item, index) => (

163

<SortableItem

164

key={item.id}

165

index={index}

166

value={item.value}

167

disabled={!userCanSort}

168

/>

169

))}

170

```

171

172

### Component Integration

173

174

#### Passing Props Through

175

176

All props except `index`, `collection`, and `disabled` are passed through to the wrapped component.

177

178

```typescript

179

const SortableProductCard = SortableElement(({

180

product,

181

onAddToCart,

182

onViewDetails,

183

className

184

}) => (

185

<div className={`product-card ${className}`}>

186

<img src={product.image} alt={product.name} />

187

<h3>{product.name}</h3>

188

<p>${product.price}</p>

189

<button onClick={() => onAddToCart(product)}>Add to Cart</button>

190

<button onClick={() => onViewDetails(product)}>View Details</button>

191

</div>

192

));

193

194

// Usage with additional props

195

<SortableProductCard

196

key={product.id}

197

index={index}

198

product={product}

199

className="featured"

200

onAddToCart={handleAddToCart}

201

onViewDetails={handleViewDetails}

202

/>

203

```

204

205

#### Prop Renaming Pattern

206

207

To access sortable-specific props in your component, pass them with different names:

208

209

```typescript

210

const SortableListItem = SortableElement(({

211

value,

212

sortIndex, // Renamed from index

213

isDisabled // Renamed from disabled

214

}) => (

215

<li className={isDisabled ? 'disabled' : ''}>

216

{value} - Position: {sortIndex}

217

</li>

218

));

219

220

// Usage

221

<SortableListItem

222

key={item.id}

223

index={index}

224

sortIndex={index} // Pass index again with different name

225

disabled={item.locked}

226

isDisabled={item.locked} // Pass disabled again with different name

227

value={item.value}

228

/>

229

```

230

231

### Event Handling

232

233

Sortable elements can contain interactive elements, but event handling requires careful consideration:

234

235

```typescript

236

const InteractiveSortableItem = SortableElement(({

237

item,

238

onEdit,

239

onDelete

240

}) => (

241

<div className="sortable-item">

242

<span>{item.title}</span>

243

<div className="actions">

244

<button

245

onClick={(e) => {

246

e.preventDefault(); // Prevent drag

247

onEdit(item);

248

}}

249

>

250

Edit

251

</button>

252

<button

253

onClick={(e) => {

254

e.preventDefault(); // Prevent drag

255

onDelete(item);

256

}}

257

>

258

Delete

259

</button>

260

</div>

261

</div>

262

));

263

```

264

265

### withRef Configuration

266

267

```typescript { .api }

268

interface Config {

269

/** Enable access to wrapped component instance */

270

withRef: boolean;

271

}

272

```

273

274

**withRef Example:**

275

276

```typescript

277

const SortableItem = SortableElement(ItemComponent, { withRef: true });

278

279

// Access wrapped instance

280

const itemRef = useRef();

281

const wrappedInstance = itemRef.current?.getWrappedInstance();

282

283

<SortableItem

284

ref={itemRef}

285

index={index}

286

value={value}

287

/>

288

```

289

290

### Advanced Patterns

291

292

#### Dynamic Collections

293

294

```typescript

295

const [items, setItems] = useState([

296

{ id: 1, text: 'Item 1', category: 'A' },

297

{ id: 2, text: 'Item 2', category: 'B' },

298

{ id: 3, text: 'Item 3', category: 'A' },

299

]);

300

301

const handleSortEnd = ({ oldIndex, newIndex, collection }) => {

302

// Sort within specific collection

303

const categoryItems = items.filter(item => item.category === collection);

304

const otherItems = items.filter(item => item.category !== collection);

305

306

const sortedCategoryItems = arrayMove(categoryItems, oldIndex, newIndex);

307

setItems([...otherItems, ...sortedCategoryItems]);

308

};

309

310

{items.map((item, index) => (

311

<SortableItem

312

key={item.id}

313

index={items.filter(i => i.category === item.category).indexOf(item)}

314

collection={item.category}

315

value={item.text}

316

/>

317

))}

318

```

319

320

#### Conditional Rendering

321

322

```typescript

323

const ConditionalSortableItem = ({ item, index, showDetails }) => {

324

const ItemComponent = ({ value, details }) => (

325

<div className="item">

326

<span>{value}</span>

327

{showDetails && <div className="details">{details}</div>}

328

</div>

329

);

330

331

const SortableItemComponent = SortableElement(ItemComponent);

332

333

return (

334

<SortableItemComponent

335

index={index}

336

value={item.title}

337

details={item.description}

338

/>

339

);

340

};

341

```