UNPKG

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
# React Transformable Alpha - Not prod ready A React library for creating transformable, draggable, resizable, and rotatable elements with multi-selection support. [![npm version](https://badge.fury.io/js/react-transformable.svg)](https://badge.fury.io/js/react-transformable) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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