use-transformable
v0.0.1
Published
A React library for creating transformable, draggable, resizable, and rotatable elements with multi-selection support
Maintainers
Readme
React Transformable
Alpha - Not prod ready
A React library for creating transformable, draggable, resizable, and rotatable elements with multi-selection support.
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
npm install react-transformable
# or
yarn add react-transformableBasic Usage
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 elementsupdateElement: (id: string, updates: Partial<Transformable>) => void- Function to update an elementgridSize?: number- Grid size for snapping (in pixels), defaults to 1 (no snapping)snapToGrid?: boolean- Whether to snap elements to grid, defaults to falsetheme?: TransformableTheme- Custom theme for stylingshowOrigin?: 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 renderchildren?: React.ReactNode- The content to render inside the element
Usage
// 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
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
<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
{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
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
Publishing to npm
To publish this library to npm:
- Update version: Update the version in
package.json - Build: Run
npm run build:libto build the library - Test: Run
npm publish --dry-runto see what would be published - Publish: Run
npm publishto publish to npm
Build Process
The library is built using Vite's library mode and includes:
- ESM and CommonJS formats
- TypeScript declarations (
.d.tsfiles) - 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 fileLicense
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
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:
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
<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:
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:
<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
originproperty - Rotates with the element to maintain visual alignment
- Customizable through the theme system
- High z-index to ensure visibility above other elements
