-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from fac30/outfit-maker
Outfit maker
- Loading branch information
Showing
3 changed files
with
548 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import React, { useState } from "react"; | ||
|
||
interface CanvasProps { | ||
items: { name: string; image: string; x: number; y: number }[]; | ||
onDeleteItem: (index: number) => void; | ||
onUpdateItemPosition: (index: number, x: number, y: number) => void; | ||
} | ||
|
||
const Canvas: React.FC<CanvasProps> = ({ items, onDeleteItem, onUpdateItemPosition }) => { | ||
const [draggingItemIndex, setDraggingItemIndex] = useState<number | null>(null); | ||
const [offset, setOffset] = useState<{ x: number; y: number }>({ x: 0, y: 0 }); | ||
|
||
// Handle the start of the dragging | ||
const handleDragStart = (index: number, e: React.MouseEvent | React.TouchEvent) => { | ||
setDraggingItemIndex(index); | ||
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX; | ||
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY; | ||
|
||
const item = items[index]; | ||
setOffset({ | ||
x: clientX - item.x, | ||
y: clientY - item.y, | ||
}); | ||
|
||
// Prevent default behavior to avoid unwanted scrolling or text selection during drag | ||
e.preventDefault(); | ||
}; | ||
|
||
// Handle the dragging movement | ||
const handleDragMove = (e: MouseEvent | TouchEvent) => { | ||
if (draggingItemIndex !== null) { | ||
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX; | ||
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY; | ||
|
||
const newX = clientX - offset.x; | ||
const newY = clientY - offset.y; | ||
|
||
// Update the item position while dragging | ||
onUpdateItemPosition(draggingItemIndex, newX, newY); | ||
} | ||
}; | ||
|
||
// Handle the end of the drag | ||
const handleDragEnd = () => { | ||
setDraggingItemIndex(null); // Stop dragging | ||
}; | ||
|
||
// Attach mouse and touch event listeners | ||
React.useEffect(() => { | ||
if (draggingItemIndex !== null) { | ||
document.addEventListener("mousemove", handleDragMove); | ||
document.addEventListener("mouseup", handleDragEnd); | ||
document.addEventListener("touchmove", handleDragMove, { passive: false }); | ||
document.addEventListener("touchend", handleDragEnd); | ||
} else { | ||
document.removeEventListener("mousemove", handleDragMove); | ||
document.removeEventListener("mouseup", handleDragEnd); | ||
document.removeEventListener("touchmove", handleDragMove); | ||
document.removeEventListener("touchend", handleDragEnd); | ||
} | ||
|
||
return () => { | ||
document.removeEventListener("mousemove", handleDragMove); | ||
document.removeEventListener("mouseup", handleDragEnd); | ||
document.removeEventListener("touchmove", handleDragMove); | ||
document.removeEventListener("touchend", handleDragEnd); | ||
}; | ||
}, [draggingItemIndex, offset]); | ||
|
||
// Handle item deletion on mouse or touch events | ||
const handleDeleteItem = (index: number, e: React.MouseEvent | React.TouchEvent) => { | ||
e.stopPropagation(); // Prevent the event from bubbling up to the parent component | ||
onDeleteItem(index); // Call the delete function passed down as a prop | ||
}; | ||
|
||
return ( | ||
<div | ||
className="canvas-container" | ||
style={{ | ||
width: "450px", // Fixed width | ||
height: "350px", // Fixed height | ||
border: "1px solid #ddd", // Optional for clarity | ||
overflow: "hidden", // Prevent items from overflowing | ||
position: "relative", // Allows absolute positioning of items | ||
marginTop: "20px", // Some spacing for clarity | ||
background: "url('/images/grid-lines.png')", // Add grid lines image to the background | ||
backgroundSize: "20px 20px", // Adjust size of grid cells | ||
}} | ||
> | ||
{items.map((item, index) => ( | ||
<div | ||
key={index} | ||
style={{ | ||
position: "absolute", | ||
top: `${item.y}px`, // Item's vertical position | ||
left: `${item.x}px`, // Item's horizontal position | ||
cursor: "pointer", | ||
}} | ||
onMouseDown={(e) => handleDragStart(index, e)} // Start dragging with mouse | ||
onTouchStart={(e) => handleDragStart(index, e)} // Start dragging with touch | ||
> | ||
<img | ||
src={item.image} | ||
alt={item.name} | ||
style={{ | ||
width: "50px", // Fixed width for each item | ||
height: "50px", // Fixed height for each item | ||
objectFit: "cover", // Keep aspect ratio for images | ||
}} | ||
/> | ||
<p style={{ textAlign: "center", fontSize: "12px" }}>{item.name}</p> | ||
|
||
{/* Add delete button for each item */} | ||
<button | ||
onClick={(e) => handleDeleteItem(index, e)} | ||
onTouchStart={(e) => handleDeleteItem(index, e)} // Handle touch events for delete | ||
style={{ | ||
position: "absolute", | ||
top: "-10px", | ||
right: "-10px", | ||
backgroundColor: "red", | ||
color: "#fff", | ||
border: "none", | ||
borderRadius: "50%", | ||
width: "20px", | ||
height: "20px", | ||
display: "flex", | ||
justifyContent: "center", | ||
alignItems: "center", | ||
cursor: "pointer", | ||
}} | ||
> | ||
X | ||
</button> | ||
</div> | ||
))} | ||
</div> | ||
); | ||
}; | ||
|
||
export default Canvas; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import React from "react"; | ||
|
||
interface CategoryMenuProps { | ||
selectedCategory: string | null; | ||
selectedSubCategory: string | null; | ||
onCategorySelect: (category: string) => void; | ||
onBackToCategories: () => void; | ||
onItemSelect: (item: { name: string; image: string }) => void; | ||
setSelectedSubCategory: (subCategory: string | null) => void; | ||
} | ||
|
||
const categories: Record< | ||
"Tops" | "Bottoms" | "Shoes", | ||
Record<string, { name: string; image: string }[]> | ||
> = { | ||
Tops: { | ||
TShirts: [ | ||
{ name: "Red T-Shirt", image: "https://image.uniqlo.com/UQ/ST3/WesternCommon/imagesgoods/465751/item/goods_17_465751_3x4.jpg?width=400" }, | ||
{ name: "Blue T-Shirt", image: "https://image.uniqlo.com/UQ/ST3/WesternCommon/imagesgoods/465751/item/goods_61_465751_3x4.jpg?width=400" }, | ||
], | ||
Hoodies: [ | ||
{ name: "Grey Hoodie", image: "https://image.uniqlo.com/UQ/ST3/WesternCommon/imagesgoods/475855/sub/goods_475855_sub14_3x4.jpg?width=400" }, | ||
{ name: "Black Hoodie", image: "https://image.uniqlo.com/UQ/ST3/WesternCommon/imagesgoods/456261/item/goods_09_456261_3x4.jpg?width=400" }, | ||
], | ||
}, | ||
Bottoms: { | ||
Jeans: [ | ||
{ name: "Blue Jeans", image: "https://image.uniqlo.com/UQ/ST3/WesternCommon/imagesgoods/464744/item/goods_64_464744_3x4.jpg?width=400" }, | ||
{ name: "Black Jeans", image: "https://image.uniqlo.com/UQ/ST3/WesternCommon/imagesgoods/471374/sub/goods_471374_sub14_3x4.jpg?width=400" }, | ||
], | ||
Shorts: [ | ||
{ name: "Denim Shorts", image: "https://image.uniqlo.com/UQ/ST3/WesternCommon/imagesgoods/464945001/sub/goods_464945001_sub14_3x4.jpg?width=400" }, | ||
{ name: "Khaki Shorts", image: "https://image.uniqlo.com/UQ/ST3/WesternCommon/imagesgoods/467052/item/goods_33_467052_3x4.jpg?width=400" }, | ||
], | ||
}, | ||
Shoes: { | ||
Sneakers: [ | ||
{ name: "White Sneakers", image: "https://d2ob0iztsaxy5v.cloudfront.net/product/340740/3407401060_zm.jpg" }, | ||
{ name: "Black Sneakers", image: "https://d2ob0iztsaxy5v.cloudfront.net/product/190032/1900327270_zm.jpg" }, | ||
], | ||
Boots: [ | ||
{ name: "Brown Boots", image: "https://d2ob0iztsaxy5v.cloudfront.net/product/149501/1495016020_zm.jpg" }, | ||
{ name: "Black Boots", image: "https://d2ob0iztsaxy5v.cloudfront.net/product/141179/1411797060_zm.jpg" }, | ||
], | ||
}, | ||
}; | ||
|
||
const CategoryMenu: React.FC<CategoryMenuProps> = ({ | ||
selectedCategory, | ||
selectedSubCategory, | ||
onCategorySelect, | ||
onBackToCategories, | ||
onItemSelect, | ||
setSelectedSubCategory, | ||
}) => { | ||
const renderMainCategories = () => ( | ||
<div> | ||
<h2>Select a Category</h2> | ||
<ul style={{ listStyle: "none", padding: 0 }}> | ||
{Object.keys(categories).map((category) => ( | ||
<li key={category}> | ||
<button | ||
style={{ margin: "10px", padding: "10px", cursor: "pointer" }} | ||
onClick={() => onCategorySelect(category)} | ||
> | ||
{category} | ||
</button> | ||
</li> | ||
))} | ||
</ul> | ||
</div> | ||
); | ||
|
||
const renderSubCategories = () => ( | ||
<div> | ||
<h2>{selectedCategory} Subcategories</h2> | ||
<button | ||
style={{ margin: "10px", padding: "10px", cursor: "pointer" }} | ||
onClick={onBackToCategories} | ||
> | ||
Back to Categories | ||
</button> | ||
<ul style={{ listStyle: "none", padding: 0 }}> | ||
{Object.keys(categories[selectedCategory as "Tops" | "Bottoms" | "Shoes"]).map( | ||
(subCategory) => ( | ||
<li key={subCategory}> | ||
<button | ||
style={{ margin: "10px", padding: "10px", cursor: "pointer" }} | ||
onClick={() => setSelectedSubCategory(subCategory)} | ||
> | ||
{subCategory} | ||
</button> | ||
</li> | ||
) | ||
)} | ||
</ul> | ||
</div> | ||
); | ||
|
||
const renderItems = () => ( | ||
<div> | ||
<h2>{selectedSubCategory} Items</h2> | ||
<button | ||
style={{ margin: "10px", padding: "10px", cursor: "pointer" }} | ||
onClick={() => setSelectedSubCategory(null)} | ||
> | ||
Back to {selectedCategory} | ||
</button> | ||
<ul style={{ listStyle: "none", padding: 0, display: "flex", flexWrap: "wrap" }}> | ||
{categories[selectedCategory as "Tops" | "Bottoms" | "Shoes"][ | ||
selectedSubCategory! | ||
].map((item) => ( | ||
<li key={item.name} style={{ margin: "10px" }}> | ||
<img | ||
src={item.image} | ||
alt={item.name} | ||
style={{ width: "100px", height: "100px", objectFit: "cover", cursor: "pointer" }} | ||
onClick={() => onItemSelect(item)} | ||
/> | ||
<p style={{ textAlign: "center" }}>{item.name}</p> | ||
</li> | ||
))} | ||
</ul> | ||
</div> | ||
); | ||
|
||
return ( | ||
<div style={{ border: "1px solid #ddd", padding: "20px", borderRadius: "5px" }}> | ||
{selectedCategory ? ( | ||
selectedSubCategory ? ( | ||
renderItems() | ||
) : ( | ||
renderSubCategories() | ||
) | ||
) : ( | ||
renderMainCategories() | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default CategoryMenu; |
Oops, something went wrong.