add-object-button.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. "use client";
  2. import { useState, useRef, useEffect } from "react";
  3. import {
  4. Plus,
  5. Box,
  6. Circle,
  7. Cylinder,
  8. Triangle,
  9. Sun,
  10. User,
  11. Volume2,
  12. Image,
  13. Video,
  14. Package,
  15. Users,
  16. Hexagon,
  17. Spline,
  18. Pentagon,
  19. Shapes,
  20. } from "lucide-react";
  21. import { useEditorStore } from "@/lib/store";
  22. import { useIsMobile } from "@/lib/use-mobile";
  23. import type { ObjectType } from "@/lib/types";
  24. const sections = [
  25. {
  26. label: "Basic Shapes",
  27. items: [
  28. { type: "box" as ObjectType, label: "Box", icon: Box },
  29. { type: "sphere" as ObjectType, label: "Sphere", icon: Circle },
  30. { type: "cylinder" as ObjectType, label: "Cylinder", icon: Cylinder },
  31. { type: "cone" as ObjectType, label: "Cone", icon: Triangle },
  32. { type: "torus" as ObjectType, label: "Torus", icon: Circle },
  33. { type: "plane" as ObjectType, label: "Plane", icon: Box },
  34. ],
  35. },
  36. {
  37. label: "Advanced Shapes",
  38. items: [
  39. { type: "capsule" as ObjectType, label: "Capsule", icon: Cylinder },
  40. {
  41. type: "tetrahedron" as ObjectType,
  42. label: "Tetrahedron",
  43. icon: Triangle,
  44. },
  45. { type: "octahedron" as ObjectType, label: "Octahedron", icon: Circle },
  46. {
  47. type: "dodecahedron" as ObjectType,
  48. label: "Dodecahedron",
  49. icon: Circle,
  50. },
  51. { type: "icosahedron" as ObjectType, label: "Icosahedron", icon: Circle },
  52. { type: "knot" as ObjectType, label: "Knot", icon: Circle },
  53. ],
  54. },
  55. {
  56. label: "Custom Geometry",
  57. items: [
  58. { type: "extrude" as ObjectType, label: "Extrude", icon: Hexagon },
  59. { type: "tube" as ObjectType, label: "Tube", icon: Spline },
  60. { type: "shape" as ObjectType, label: "Shape", icon: Pentagon },
  61. { type: "mesh" as ObjectType, label: "Mesh", icon: Shapes },
  62. ],
  63. },
  64. {
  65. label: "Environment",
  66. items: [
  67. { type: "light" as ObjectType, label: "Light", icon: Sun },
  68. { type: "sound" as ObjectType, label: "Sound", icon: Volume2 },
  69. ],
  70. },
  71. {
  72. label: "Media",
  73. items: [
  74. { type: "image" as ObjectType, label: "Image", icon: Image },
  75. { type: "video" as ObjectType, label: "Video", icon: Video },
  76. ],
  77. },
  78. {
  79. label: "Entities",
  80. items: [
  81. { type: "player" as ObjectType, label: "Player", icon: User },
  82. { type: "character" as ObjectType, label: "Character", icon: Users },
  83. { type: "model" as ObjectType, label: "Model", icon: Package },
  84. ],
  85. },
  86. ];
  87. export function AddObjectButton() {
  88. const [open, setOpen] = useState(false);
  89. const addObject = useEditorStore((s) => s.addObject);
  90. const popoverRef = useRef<HTMLDivElement>(null);
  91. const isMobile = useIsMobile();
  92. useEffect(() => {
  93. if (!open) return;
  94. const handleClickOutside = (e: PointerEvent) => {
  95. if (
  96. popoverRef.current &&
  97. !popoverRef.current.contains(e.target as Node)
  98. ) {
  99. setOpen(false);
  100. }
  101. };
  102. document.addEventListener("pointerdown", handleClickOutside);
  103. return () =>
  104. document.removeEventListener("pointerdown", handleClickOutside);
  105. }, [open]);
  106. return (
  107. <div className="absolute bottom-4 left-4 z-10" ref={popoverRef}>
  108. {open && (
  109. <div
  110. className={`absolute bottom-14 left-0 ${isMobile ? "w-64" : "w-56"} bg-[#141414] border border-[#2a2a2a] rounded-lg shadow-xl overflow-hidden`}
  111. >
  112. <div className="max-h-80 overflow-y-auto py-1">
  113. {sections.map((section) => (
  114. <div key={section.label}>
  115. <div className="px-3 py-1.5 text-[9px] font-semibold text-[#555] uppercase tracking-wider">
  116. {section.label}
  117. </div>
  118. {section.items.map((item) => (
  119. <button
  120. key={item.type}
  121. onClick={() => {
  122. addObject(item.type);
  123. setOpen(false);
  124. }}
  125. className={`w-full flex items-center gap-2.5 px-3 ${isMobile ? "py-2.5" : "py-1.5"} text-xs text-[#aaa] hover:text-white active:text-white hover:bg-white/5 active:bg-white/10 transition-colors`}
  126. >
  127. <item.icon size={isMobile ? 16 : 12} />
  128. {item.label}
  129. </button>
  130. ))}
  131. </div>
  132. ))}
  133. </div>
  134. </div>
  135. )}
  136. <button
  137. onClick={() => setOpen(!open)}
  138. className={`${isMobile ? "w-11 h-11" : "w-9 h-9"} flex items-center justify-center rounded-full bg-white/10 hover:bg-white/20 active:bg-white/30 text-white transition-colors backdrop-blur-sm`}
  139. title="Add object"
  140. >
  141. <Plus size={isMobile ? 20 : 18} />
  142. </button>
  143. </div>
  144. );
  145. }