Interactive Diagram Editor in React with TypeScript
This challenge tasks you with building a basic, interactive diagram editor using React and TypeScript. The editor should allow users to create, move, and delete simple shapes (circles and rectangles) on a canvas. This is a common requirement in many applications, from workflow design tools to data visualization platforms, and provides a good exercise in state management, event handling, and basic UI design.
Problem Description
You are to create a React component that renders a diagram editor. The editor should:
- Shape Creation: Allow users to add circles and rectangles to the canvas. Clicking a "Add Circle" or "Add Rectangle" button should create a new shape at the mouse click coordinates.
- Shape Rendering: Display the created shapes on the canvas. Each shape should be visually distinct (e.g., different colors or outlines).
- Shape Movement: Allow users to drag and drop shapes around the canvas. Clicking and dragging a shape should update its position in real-time.
- Shape Deletion: Provide a mechanism (e.g., a delete button on each shape or a context menu) to remove shapes from the canvas.
- State Management: Maintain the state of the shapes (position, type, size) within the React component.
- Canvas Boundaries: Shapes should not be draggable outside the visible canvas area.
Key Requirements:
- Use React and TypeScript.
- Implement a clear and maintainable component structure.
- Handle mouse events (click, drag, mousemove) appropriately.
- Use appropriate styling (CSS or a CSS-in-JS library) to visually represent the shapes.
- The editor should be responsive and work well on different screen sizes.
Expected Behavior:
- Clicking "Add Circle" creates a circle at the mouse position.
- Clicking "Add Rectangle" creates a rectangle at the mouse position.
- Clicking a shape selects it for dragging.
- Dragging a selected shape moves it smoothly.
- Clicking the delete button (or using the context menu) removes the shape.
- The canvas should have a defined size.
Edge Cases to Consider:
- What happens when the user clicks outside of the canvas area?
- How do you handle overlapping shapes? (For this challenge, overlapping shapes are acceptable, but consider how you might handle them in a more complex editor.)
- What happens when the user tries to delete a shape that doesn't exist?
- How do you prevent shapes from being dragged completely off-screen?
Examples
Example 1:
Input: Initially empty canvas. User clicks "Add Circle", then clicks on the canvas at coordinates (100, 150). User then clicks and drags the circle to coordinates (200, 250).
Output: A circle is rendered at (100, 150) initially, then moves to (200, 250) when dragged.
Explanation: The component updates the circle's position in its state based on the mouse drag event.
Example 2:
Input: Canvas with one circle and one rectangle. User clicks "Add Rectangle", then clicks on the canvas at coordinates (50, 75). User then clicks the delete button on the rectangle.
Output: The rectangle disappears from the canvas, and only the circle remains.
Explanation: The component removes the rectangle from its state and re-renders the canvas.
Example 3: (Edge Case)
Input: Canvas with a circle. User clicks and drags the circle towards the edge of the canvas.
Output: The circle stops moving when it reaches the edge of the canvas and does not go beyond it.
Explanation: The component prevents the circle from being dragged outside the canvas boundaries.
Constraints
- Canvas Size: The canvas should be 800px wide and 600px high.
- Shape Size: Circles should have a default radius of 25px. Rectangles should have a default width of 50px and a default height of 30px.
- Performance: The editor should remain responsive even with a moderate number of shapes (e.g., up to 20). Avoid unnecessary re-renders.
- Shape Types: Only circles and rectangles are supported.
- Input Format: User interaction is solely through mouse clicks and drags.
Notes
- Consider using a library like
uuidto generate unique IDs for each shape. This can be helpful for identifying shapes during deletion or other operations. - Think about how you will manage the state of the selected shape.
- You can use CSS or a CSS-in-JS library (like styled-components or emotion) for styling.
- Focus on the core functionality of creating, moving, and deleting shapes. Advanced features like shape resizing, connecting shapes, or custom shape types are not required for this challenge.
- Break down the problem into smaller, manageable components. For example, you could have a
Shapecomponent that renders a single shape and handles its dragging behavior. - Consider using React's
useRefhook to access the DOM element of the canvas.