use-transformable
Version:
A React library for creating transformable, draggable, resizable, and rotatable elements with multi-selection support
418 lines (341 loc) • 11.1 kB
Markdown
# React Transformable
Alpha - Not prod ready
A React library for creating transformable, draggable, resizable, and rotatable elements with multi-selection support.
[](https://badge.fury.io/js/react-transformable)
[](https://opensource.org/licenses/MIT)
## Features
- 🖱️ **Drag & Drop**: Smooth dragging with grid snapping support
- 📏 **Resize**: Resize from any corner or edge with constraints
- 🔄 **Rotate**: Rotate elements around custom origin points
- ✨ **Multi-Selection**: Select and transform multiple elements at once
- 🎨 **Customizable**: Theme support and custom styling
- 📱 **Touch Support**: Works on mobile devices
- 🔧 **TypeScript**: Full TypeScript support
- ⚡ **Lightweight**: No heavy dependencies
## Installation
```bash
npm install react-transformable
# or
yarn add react-transformable
```
## Basic Usage
```tsx
import React, { useState } from 'react';
import {
TransformableProvider,
TransformableItem,
useTransformable,
type Transformable
} from 'react-transformable';
// Define your custom element type
type CustomElement = Transformable & {
content?: string;
color?: string;
};
function MyApp() {
const [elements, setElements] = useState<CustomElement[]>([
{
id: '1',
x: 100,
y: 100,
width: 120,
height: 80,
rotation: 0,
origin: [0, 0],
zIndex: 0,
content: 'Element 1',
color: '#e3f2fd'
},
{
id: '2',
x: 300,
y: 150,
width: 100,
height: 100,
rotation: 45,
origin: [0, 0],
zIndex: 0,
content: 'Element 2',
color: '#f3e5f5'
},
]);
const updateElement = (id: string, updates: Partial<CustomElement>) => {
setElements(prev => prev.map(el =>
el.id === id ? { ...el, ...updates } : el
));
};
return (
<TransformableProvider
elements={elements}
updateElement={updateElement}
>
<div className="canvas" data-canvas style={{ width: '800px', height: '600px', border: '1px solid #ccc' }}>
{elements.map(element => (
<TransformableItem
key={element.id}
id={element.id}
>
<div style={{
backgroundColor: element.color,
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
{element.content}
</div>
</TransformableItem>
))}
</div>
</TransformableProvider>
);
}
```
## API Reference
### TransformableProvider
The main context provider that manages selection state and transformation logic.
#### Props
- `elements: Transformable[]` - Array of all transformable elements
- `updateElement: (id: string, updates: Partial<Transformable>) => void` - Function to update an element
- `gridSize?: number` - Grid size for snapping (in pixels), defaults to 1 (no snapping)
- `snapToGrid?: boolean` - Whether to snap elements to grid, defaults to false
- `theme?: TransformableTheme` - Custom theme for styling
- `showOrigin?: boolean` - Whether to show transform origin indicators
### TransformableItem
A single transformable element component that automatically reads all necessary data from the TransformableProvider context.
#### Props
- `id: string` - The unique identifier of the element to render
- `children?: React.ReactNode` - The content to render inside the element
#### Usage
```tsx
// Simple text content
<TransformableItem id="element-1">
{element.id}
</TransformableItem>
// Custom HTML content
<TransformableItem id="element-2">
<div style={{ padding: '10px' }}>
<h3>Custom Title</h3>
<p>Custom description</p>
</div>
</TransformableItem>
// Image content
<TransformableItem id="element-3">
<img src={element.src} alt="Custom image" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
</TransformableItem>
```
**Note**: TransformableItem automatically reads the element's transformation data (position, size, rotation, etc.) from the context using the provided `id`. No need to pass element data as props!
### Transformable Interface
```typescript
interface Transformable {
id: string;
x: number;
y: number;
width: number;
height: number;
rotation: number;
origin: [number, number]; // Transform origin [x, y] as percentages
zIndex: number;
minWidth?: number; // Minimum width constraint (allows negative values if not set)
minHeight?: number; // Minimum height constraint (allows negative values if not set)
}
// For custom elements, extend the base interface:
type CustomElement = Transformable & {
content?: string;
color?: string;
// ... your custom properties
};
```
## Usage Examples
### Custom Element Content
```tsx
<TransformableItem
element={element}
isSelected={selectedIds.has(element.id)}
>
<div style={{ padding: '10px' }}>
<h3>Custom Content</h3>
<p>This element has custom content!</p>
</div>
</TransformableItem>
```
### Different Element Types
```tsx
{elements.map(element => (
<TransformableItem
key={element.id}
element={element}
isSelected={selectedIds.has(element.id)}
>
{element.type === 'image' ? (
<img src={element.src} alt={element.alt} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
) : element.type === 'text' ? (
<div style={{ padding: '10px', fontSize: element.fontSize }}>
{element.id}
</div>
) : (
element.id
)}
</TransformableItem>
))}
```
## Features in Detail
### Multi-Selection
- **Single Selection**: Click on an element to select it
- **Multi-Selection**: Hold Shift and click to add/remove elements from selection
- **Selection Box**: Drag on empty space to create a selection box (coming soon)
### Transformation Handles
- **Resize Handles**: White squares on corners and edges for resizing
- **Rotation Handles**: Invisible handles beyond corners for rotation
- **Drag Area**: Click and drag anywhere on the element to move it
### Grid Snapping
- Toggle grid snapping on/off
- Configure grid size
- Elements snap to grid during drag, resize, and rotate operations
### Real-time Feedback
- All transformations provide immediate visual feedback
- Multi-drag shows all selected elements moving together
- Smooth animations and responsive interactions
## Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request
## Publishing to npm
To publish this library to npm:
1. **Update version**: Update the version in `package.json`
2. **Build**: Run `npm run build:lib` to build the library
3. **Test**: Run `npm publish --dry-run` to see what would be published
4. **Publish**: Run `npm publish` to publish to npm
### Build Process
The library is built using Vite's library mode and includes:
- **ESM** and **CommonJS** formats
- **TypeScript declarations** (`.d.ts` files)
- **Source maps** for debugging
- **Tree-shaking** support
### Package Structure
```
react-transformable/
├── dist/
│ ├── index.esm.js # ES Module bundle
│ ├── index.js # CommonJS bundle
│ ├── index.d.ts # TypeScript declarations
│ └── themes/
│ └── index.d.ts # Theme declarations
├── src/ # Source code
├── demo/ # Demo application
└── README.md # This file
```
## License
MIT
## Theming
The library supports comprehensive theming for all visual aspects of transformable items. You can customize element outlines, resize handlers, rotation handlers, and origin indicators with ease.
### Styling Units
All borders and outlines use `px` units for consistent visual thickness. The library is designed to work well at various zoom levels while maintaining the intended visual appearance.
### Basic Theme Structure
```typescript
import { TransformableProvider, type TransformableTheme } from 'react-transformable';
const theme: TransformableTheme = {
element: {
outline: '2px solid #ff0000',
outlineOffset: '2px',
boxShadow: '0 0 10px rgba(255, 0, 0, 0.3)',
},
resizeHandlers: {
base: {
backgroundColor: '#ff6b6b',
outline: '2px solid #fff',
borderRadius: '50%',
width: 12,
height: 12,
},
// Override specific handlers if needed
'top-left': {
backgroundColor: '#4ecdc4', // Different color for top-left
},
},
rotationHandler: {
base: {
backgroundColor: '#4ecdc4',
border: '2px solid #fff',
borderRadius: '50%',
width: 12,
height: 12,
},
},
origin: {
backgroundColor: '#ff6b6b',
border: '2px solid #fff',
width: '8px',
height: '8px',
},
};
```
### Using Base Styles for Efficiency
The theme system supports base styles that apply to all handlers, making customization efficient:
```typescript
const efficientTheme: TransformableTheme = {
resizeHandlers: {
base: {
backgroundColor: '#ff6b6b',
outline: '2px solid #fff',
borderRadius: '50%',
width: 12,
height: 12,
},
// Only override what's different
'top-left': { backgroundColor: '#4ecdc4' },
'bottom-right': { backgroundColor: '#4ecdc4' },
},
};
```
### Applying Themes
```tsx
<TransformableProvider
selected={selectedIds}
setSelectedIds={setSelectedIds}
gridSize={gridSize}
snapToGrid={snapToGrid}
elements={elements}
updateElement={updateElement}
theme={theme} // Pass your custom theme
>
{/* Your transformable items will use the custom theme */}
</TransformableProvider>
```
### Default Theme
If no theme is provided, the library uses a default theme with the original styling. You can also import and extend the default theme:
```typescript
import { defaultTheme, type TransformableTheme } from 'react-transformable';
const customTheme: TransformableTheme = {
...defaultTheme, // Start with all defaults
element: {
...defaultTheme.element,
outline: '2px solid #ff0000', // Override just the outline
},
};
```
### Origin Display
The library can display origin indicators for elements, which are useful for understanding the rotation and transformation center points:
```tsx
<TransformableProvider
selected={selectedIds}
setSelectedIds={setSelectedIds}
gridSize={gridSize}
snapToGrid={snapToGrid}
elements={elements}
updateElement={updateElement}
showOrigin={true} // Enable origin display
theme={customTheme} // Optional: customize origin appearance
>
{/* Your transformable items will show origin indicators */}
</TransformableProvider>
```
**Origin Indicator Features:**
- Shows the exact center point of rotation for each element
- Positioned based on the element's `origin` property
- Rotates with the element to maintain visual alignment
- Customizable through the theme system
- High z-index to ensure visibility above other elements