diff --git a/app/.github/ISSUE_TEMPLATE/bug_report.md b/app/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..e6f76b0 --- /dev/null +++ b/app/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug Report +about: Report a bug or issue with the project +title: "[Bug] " +labels: bug +assignees: '' + +--- + +### Bug Description + +Provide a detailed description of the bug or issue. + + +### Steps to Reproduce + +1. Step one +2. Step two +3. Step three + +### Expected Behavior + +Explain what you expected to happen. + + +### Actual Behavior + +Describe what actually happened, including any error messages or logs. + + +### Screenshots (if applicable) + +If applicable, add screenshots to help explain your problem. + + +### Environment Information: + +- Operating System: [e.g., macOS, Windows, Linux] +- Browser (if applicable): [e.g., Chrome, Firefox] +- Version: [e.g., 1.0.0] diff --git a/app/.github/ISSUE_TEMPLATE/feature_request.md b/app/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..9710564 --- /dev/null +++ b/app/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature Request +about: Suggest a new feature for the project +title: "[Feature Request] " +labels: enhancement +assignees: '' + +--- + +### Feature Request Description + +Provide a clear and concise description of the feature you’d like to request. + + +### Why Is This Feature Needed? + +Explain why the feature would be useful or necessary. + + +### Additional Context + +Add any other context or screenshots that may help describe the feature request. diff --git a/app/.github/PULL_REQUEST_TEMPLATE/bug_fix.md b/app/.github/PULL_REQUEST_TEMPLATE/bug_fix.md new file mode 100644 index 0000000..668e77c --- /dev/null +++ b/app/.github/PULL_REQUEST_TEMPLATE/bug_fix.md @@ -0,0 +1,24 @@ +### Description + +Please include a summary of the changes and the motivation behind them. Also include any relevant context or links to related issues. + +**Related Issues:** + +- Issue # (if applicable) +- Link to related issue or ticket (if applicable) + +### Changes Made + +- Describe what changes were made in this pull request. +- Include any relevant details about the implementation. + +### Checklist + +- [ ] I have tested my changes locally. +- [ ] I have updated the documentation (if applicable). +- [ ] I have included unit tests (if applicable). +- [ ] My code follows the project's coding style. + +### Screenshots (if applicable) + +![screenshot](link_to_screenshot) diff --git a/app/.github/PULL_REQUEST_TEMPLATE/documentation.md b/app/.github/PULL_REQUEST_TEMPLATE/documentation.md new file mode 100644 index 0000000..7e3e5d4 --- /dev/null +++ b/app/.github/PULL_REQUEST_TEMPLATE/documentation.md @@ -0,0 +1,24 @@ +--- +name: Documentation Update +about: Submit a pull request for updating documentation +title: "[Docs] " +labels: documentation, needs review +assignees: '' + +--- + +### Documentation Changes + +Please describe the changes you are making to the documentation. + +### Related Code Changes (if applicable) + +If your documentation relates to any new code changes, please provide context or links to those changes. + +### Reason for Change + +Why are these documentation updates necessary? + +### Screenshots (if applicable) + +If updating visual elements of documentation, provide screenshots or examples. diff --git a/app/.github/PULL_REQUEST_TEMPLATE/feature_request.md b/app/.github/PULL_REQUEST_TEMPLATE/feature_request.md new file mode 100644 index 0000000..d7b6a18 --- /dev/null +++ b/app/.github/PULL_REQUEST_TEMPLATE/feature_request.md @@ -0,0 +1,28 @@ +--- +name: Feature Request +about: Submit a pull request for adding a new feature +title: "[Feature] " +labels: enhancement, needs review +assignees: '' + +--- + +### Description of the Feature + +Briefly describe the new feature you are implementing. + +**Related Issue:** (if any) + +### Changes Made + +1. context-menu-list the specific changes made in the code. +2. Explain the new functionality and how it improves the project. + +### Testing and Documentation + +1. Steps for testing the new feature. +2. Any documentation updates made. + +### Screenshots (if applicable) + +Add any screenshots or visuals demonstrating the feature. diff --git a/app/src/styles/layout/aside.scss b/app/.github/workflows/ci.yml similarity index 100% rename from app/src/styles/layout/aside.scss rename to app/.github/workflows/ci.yml diff --git a/app/.github/workflows/deploy-docs.yml b/app/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..7d82649 --- /dev/null +++ b/app/.github/workflows/deploy-docs.yml @@ -0,0 +1,24 @@ +name: Deploy Docsify + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Install dependencies + run: npm install + + - name: Build Docsify site + run: docsify serve docs --port 4000 diff --git a/app/src/components/icons/ContextMenuIcons.tsx b/app/src/components/icons/ContextMenuIcons.tsx index 218cd81..a1ced1b 100644 --- a/app/src/components/icons/ContextMenuIcons.tsx +++ b/app/src/components/icons/ContextMenuIcons.tsx @@ -90,7 +90,7 @@ export function FlipZAxisIcon() { fill="none" xmlns="http://www.w3.org/2000/svg" > - + - + - + - + - + - + ); } + +export function ResizeHeightIcon() { + return ( + + + + + + + + + + + ); +} + +export function RemoveIcon() { + return ( + + + + ); +} + +export function InfoIcon() { + return ( + + + + + ); +} diff --git a/app/src/components/icons/ExportToolsIcons.tsx b/app/src/components/icons/ExportToolsIcons.tsx index dcf75ce..afffc88 100644 --- a/app/src/components/icons/ExportToolsIcons.tsx +++ b/app/src/components/icons/ExportToolsIcons.tsx @@ -7,7 +7,7 @@ export function ZoneIcon({ isActive }: { isActive: boolean }) { fill="none" xmlns="http://www.w3.org/2000/svg" > - + - + - + - - + + + + ); +} + +export function AppDockIcon() { + return ( + + + + + + + + + + ); } diff --git a/app/src/components/icons/SimulationIcons.tsx b/app/src/components/icons/SimulationIcons.tsx new file mode 100644 index 0000000..1dc82a7 --- /dev/null +++ b/app/src/components/icons/SimulationIcons.tsx @@ -0,0 +1,70 @@ +export function AnalysisIcon({ isActive }: { isActive: boolean }) { + return ( + + + + + ); +} + +export function MechanicsIcon({ isActive }: { isActive: boolean }) { + return ( + + + + + ); +} + +export function PropertiesIcon({ isActive }: { isActive: boolean }) { + return ( + + + + + + ); +} diff --git a/app/src/components/layout/sidebarLeft/Assets.tsx b/app/src/components/layout/sidebarLeft/Assets.tsx index 01e0a08..bbf476c 100644 --- a/app/src/components/layout/sidebarLeft/Assets.tsx +++ b/app/src/components/layout/sidebarLeft/Assets.tsx @@ -1,8 +1,24 @@ -import React from 'react'; +import React, { useState } from "react"; +import Search from "../../ui/inputs/Search"; const Assets: React.FC = () => { + const [searchValue, setSearchValue] = useState(""); + + const handleSearchChange = (value: string) => { + setSearchValue(value); + console.log(value); // Log the search value if needed + }; return ( -
Assets
+
+ + {searchValue ? ( +
+

Results for "{searchValue}"

+
+ ) : ( + <> + )} +
); }; diff --git a/app/src/components/layout/sidebarLeft/Header.tsx b/app/src/components/layout/sidebarLeft/Header.tsx index 71f1e75..9615470 100644 --- a/app/src/components/layout/sidebarLeft/Header.tsx +++ b/app/src/components/layout/sidebarLeft/Header.tsx @@ -1,9 +1,29 @@ -import React from 'react'; +import React from "react"; +import { ToggleSidebarIcon } from "../../icons/HeaderIcons"; +import { LogoIcon } from "../../icons/Logo"; +import FileMenu from "../../ui/FileMenu"; +import useToggleStore from "../../../store/useUIToggleStore"; const Header: React.FC = () => { + const { toggleUI, setToggleUI } = useToggleStore(); return (
- +
+
+ +
+
+ +
+
+
{ + setToggleUI(!toggleUI); + }} + > + +
); }; diff --git a/app/src/components/layout/sidebarLeft/Outline.tsx b/app/src/components/layout/sidebarLeft/Outline.tsx index a06f00a..a364947 100644 --- a/app/src/components/layout/sidebarLeft/Outline.tsx +++ b/app/src/components/layout/sidebarLeft/Outline.tsx @@ -1,8 +1,47 @@ -import React from 'react'; +import React, { useState } from "react"; +import Search from "../../ui/inputs/Search"; +import DropDownList from "../../ui/list/DropDownList"; const Outline: React.FC = () => { + const [searchValue, setSearchValue] = useState(""); + + const handleSearchChange = (value: string) => { + setSearchValue(value); + console.log(value); // Log the search value if needed + }; + + const dropdownItems = [ + { id: "1", name: "Ground Floor" }, + { id: "2", name: "Floor 1" }, + ]; // Example dropdown items + return ( -
Outline
+
+ + {searchValue ? ( +
+

Results for "{searchValue}"

+
+ ) : ( +
+ + +
+ )} +
); }; diff --git a/app/src/components/layout/sidebarLeft/SideBarLeft.tsx b/app/src/components/layout/sidebarLeft/SideBarLeft.tsx index ae56a65..f6b0842 100644 --- a/app/src/components/layout/sidebarLeft/SideBarLeft.tsx +++ b/app/src/components/layout/sidebarLeft/SideBarLeft.tsx @@ -1,23 +1,53 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import ToggleHeader from "../../ui/inputs/ToggleHeader"; -import Search from "../../ui/inputs/Search"; +import Outline from "./Outline"; +import Header from "./Header"; +import useToggleStore from "../../../store/useUIToggleStore"; +import Assets from "./Assets"; +import useModuleStore from "../../../store/useModuleStore"; const SideBarLeft: React.FC = () => { - const [activeOption, setActiveOption] = useState("Option 1"); + const [activeOption, setActiveOption] = useState("Outline"); + + const { toggleUI } = useToggleStore(); + const { activeModule } = useModuleStore(); + + // Reset activeList whenever activeModule changes + useEffect(() => { + setActiveOption("Outline"); + }, [activeModule]); const handleToggleClick = (option: string) => { setActiveOption(option); // Update the active option }; return ( - <> -
SideBarLeft
- - console.log(value)} /> - +
+
+ {toggleUI && ( +
+ {activeModule === "visualization" ? ( + <> + + + ) : ( + <> + +
+ {activeOption === "Outline" ? : } +
+ + )} +
+ )} +
); }; diff --git a/app/src/components/layout/sidebarRight/Header.tsx b/app/src/components/layout/sidebarRight/Header.tsx new file mode 100644 index 0000000..e9ebaa8 --- /dev/null +++ b/app/src/components/layout/sidebarRight/Header.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import { AppDockIcon } from "../../icons/HeaderIcons"; + +const Header: React.FC = () => { + const guestUsers = [ + { value: "Nazria", color: "#43C06D" }, + { value: "Name1", color: "#0050EB" }, + { value: "Abigail", color: "#FF6600" }, + { value: "Jack", color: "#488EF6" }, + ]; // Example guest users array + + return ( +
+
+
Share
+
+ +
+
+
+
+
+ {guestUsers.length > 3 && ( +
+{guestUsers.length - 3}
+ )} + {guestUsers.slice(0, 3).map((user, index) => ( +
+ {user.value[0]} +
+ ))} +
+
+
+ V +
+
+ +
+
+
+
+ ); +}; + +export default Header; diff --git a/app/src/components/layout/sidebarRight/SideBarRight.tsx b/app/src/components/layout/sidebarRight/SideBarRight.tsx index a5ee44f..9cac07b 100644 --- a/app/src/components/layout/sidebarRight/SideBarRight.tsx +++ b/app/src/components/layout/sidebarRight/SideBarRight.tsx @@ -1,8 +1,67 @@ -import React from 'react'; +import React, { useEffect, useState } from "react"; +import Header from "./Header"; +import useModuleStore from "../../../store/useModuleStore"; +import { + AnalysisIcon, + MechanicsIcon, + PropertiesIcon, +} from "../../icons/SimulationIcons"; +import useToggleStore from "../../../store/useUIToggleStore"; +import MachineMechanics from "./mechanics/MachineMechanics"; const SideBarRight: React.FC = () => { + const { activeModule } = useModuleStore(); + const [activeList, setActiveList] = useState("properties"); + const { toggleUI } = useToggleStore(); + + // Reset activeList whenever activeModule changes + useEffect(() => { + setActiveList("properties"); + }, [activeModule]); + return ( -
SideBarRight
+
+
+ {toggleUI && ( +
+
setActiveList("properties")} + > + +
+ {activeModule === "simulation" && ( + <> +
setActiveList("mechanics")} + > + +
+
setActiveList("analysis")} + > + +
+ + )} +
+ )} + {toggleUI && ( +
+
+ +
+
+ )} +
); }; diff --git a/app/src/styles/layout/footer.scss b/app/src/components/layout/sidebarRight/mechanics/ColiderMechanics.tsx similarity index 100% rename from app/src/styles/layout/footer.scss rename to app/src/components/layout/sidebarRight/mechanics/ColiderMechanics.tsx diff --git a/app/src/components/layout/sidebarRight/mechanics/MachineMechanics.tsx b/app/src/components/layout/sidebarRight/mechanics/MachineMechanics.tsx new file mode 100644 index 0000000..a240de8 --- /dev/null +++ b/app/src/components/layout/sidebarRight/mechanics/MachineMechanics.tsx @@ -0,0 +1,176 @@ +import React, { useState } from "react"; +import { + AddIcon, + InfoIcon, + RemoveIcon, + ResizeHeightIcon, +} from "../../../icons/ExportCommonIcons"; + +const MachineMechanics: React.FC = () => { + const [actionList, setActionList] = useState([]); + const [triggerList, setTriggerList] = useState([]); + const [selectedItem, setSelectedItem] = useState<{ + type: "action" | "trigger"; + name: string; + } | null>(null); + const [editedName, setEditedName] = useState(""); + + const handleAddAction = () => { + setActionList([...actionList, `Action ${actionList.length + 1}`]); + }; + + const handleAddTrigger = () => { + setTriggerList([...triggerList, `Trigger ${triggerList.length + 1}`]); + }; + + const handleRemoveAction = (index: number) => { + setActionList(actionList.filter((_, i) => i !== index)); + if ( + selectedItem?.type === "action" && + selectedItem.name === actionList[index] + ) { + setSelectedItem(null); + setEditedName(""); + } + }; + + const handleRemoveTrigger = (index: number) => { + setTriggerList(triggerList.filter((_, i) => i !== index)); + if ( + selectedItem?.type === "trigger" && + selectedItem.name === triggerList[index] + ) { + setSelectedItem(null); + setEditedName(""); + } + }; + + const handleSelectItem = (type: "action" | "trigger", name: string) => { + setSelectedItem({ type, name }); + setEditedName(name); + }; + + const handleSave = () => { + if (!selectedItem) return; + + if (selectedItem.type === "action") { + setActionList( + actionList.map((action) => + action === selectedItem.name ? editedName : action + ) + ); + } else if (selectedItem.type === "trigger") { + setTriggerList( + triggerList.map((trigger) => + trigger === selectedItem.name ? editedName : trigger + ) + ); + } + + setSelectedItem({ ...selectedItem, name: editedName }); + }; + + return ( +
+
+
+
Actions
+
+ Add +
+
+
+
+ {actionList.map((action, index) => ( +
+
handleSelectItem("action", action)}> + {action} +
+
handleRemoveAction(index)} + > + +
+
+ ))} +
+
+ +
+
+
+
+
+
Triggers
+
+ Add +
+
+
+
+ {triggerList.map((trigger, index) => ( +
+
handleSelectItem("trigger", trigger)}> + {trigger} +
+
handleRemoveTrigger(index)} + > + +
+
+ ))} +
+
+ +
+
+
+
+ {selectedItem && ( + <> +
+ + setEditedName(e.target.value)} + /> +
+ {/* Add other Properties Like: + * Object Selection Dropdown + * Buffer Time + * Get Value From Object + * Action + * etc. + */} +
Update
{/* remove this */} + + )} +
+
+ + By Selecting Path, you can create Object Triggers. +
+
+ ); +}; + +export default MachineMechanics; diff --git a/app/src/components/layout/sidebarRight/properties/Properties.tsx b/app/src/components/layout/sidebarRight/properties/Properties.tsx new file mode 100644 index 0000000..dc33645 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/Properties.tsx @@ -0,0 +1,9 @@ +import React from "react"; + +const GlobalProperties: React.FC = () => { + return ( +
GlobalProperties
+ ); +}; + +export default GlobalProperties; diff --git a/app/src/components/ui/FileMenu.tsx b/app/src/components/ui/FileMenu.tsx new file mode 100644 index 0000000..475d01f --- /dev/null +++ b/app/src/components/ui/FileMenu.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import RenameInput from "./inputs/RenameInput"; + +const FileMenu: React.FC = () => { + return ( +
+
+ +
+
+ ); +}; + +export default FileMenu; diff --git a/app/src/components/ui/inputs/ProjectDropDown.tsx b/app/src/components/ui/inputs/ProjectDropDown.tsx deleted file mode 100644 index 44ce3f5..0000000 --- a/app/src/components/ui/inputs/ProjectDropDown.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; - -const ProjectDropDown: React.FC = () => { - return ( -
ProjectDropDown
- ); -}; - -export default ProjectDropDown; diff --git a/app/src/components/ui/inputs/RenameInput.tsx b/app/src/components/ui/inputs/RenameInput.tsx new file mode 100644 index 0000000..cae54f3 --- /dev/null +++ b/app/src/components/ui/inputs/RenameInput.tsx @@ -0,0 +1,59 @@ +import React, { useState, useRef } from "react"; + +interface RenameInputProps { + value: string; + onRename?: (newText: string) => void; +} + +const RenameInput: React.FC = ({ value, onRename }) => { + const [isEditing, setIsEditing] = useState(false); + const [text, setText] = useState(value); + const inputRef = useRef(null); + + const handleDoubleClick = () => { + setIsEditing(true); + setTimeout(() => inputRef.current?.focus(), 0); // Focus the input after rendering + }; + + const handleBlur = () => { + setIsEditing(false); + if (onRename) { + onRename(text); + } + }; + + const handleChange = (e: React.ChangeEvent) => { + setText(e.target.value); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + setIsEditing(false); + if (onRename) { + onRename(text); + } + } + }; + + return ( + <> + {isEditing ? ( + + ) : ( + + {text} + + )} + + ); +}; + +export default RenameInput; diff --git a/app/src/components/ui/inputs/Search.tsx b/app/src/components/ui/inputs/Search.tsx index 1cdd6cc..fa9d28c 100644 --- a/app/src/components/ui/inputs/Search.tsx +++ b/app/src/components/ui/inputs/Search.tsx @@ -1,5 +1,5 @@ -import React, { ChangeEvent } from "react"; -import { SearchIcon } from "../../icons/ExportCommonIcons"; +import React, { ChangeEvent, useState } from "react"; +import { CloseIcon, SearchIcon } from "../../icons/ExportCommonIcons"; interface SearchProps { value?: string; // The current value of the search input @@ -8,25 +8,58 @@ interface SearchProps { } const Search: React.FC = ({ - value, + value = "", placeholder = "Search", onChange, }) => { - // Handle input change + // State to track the input value and focus status + const [inputValue, setInputValue] = useState(value); + const [isFocused, setIsFocused] = useState(false); + const handleInputChange = (event: ChangeEvent) => { - onChange(event.target.value); // Call the onChange prop with the new value + const newValue = event.target.value; + setInputValue(newValue); + onChange(newValue); // Call the onChange prop with the new value + }; + + const handleClear = () => { + setInputValue(""); + onChange(""); // Clear the input value + }; + + const handleFocus = () => { + setIsFocused(true); // Set focus state to true + }; + + const handleBlur = () => { + setIsFocused(false); // Set focus state to false }; return ( -
- - +
+
+
+ +
+ + {inputValue && ( + + )} +
); }; diff --git a/app/src/components/ui/list/DropDownList.tsx b/app/src/components/ui/list/DropDownList.tsx new file mode 100644 index 0000000..093765c --- /dev/null +++ b/app/src/components/ui/list/DropDownList.tsx @@ -0,0 +1,98 @@ +import React, { useState } from "react"; +import List from "./List"; +import { AddIcon, ArrowIcon, FocusIcon } from "../../icons/ExportCommonIcons"; +import KebabMenuListMultiSelect from "./KebebMenuListMultiSelect"; + +interface DropDownListProps { + value?: string; // Value to display in the DropDownList + items?: { id: string; name: string }[]; // Items to display in the dropdown list + showFocusIcon?: boolean; // Determines if the FocusIcon should be displayed + showAddIcon?: boolean; // Determines if the AddIcon should be displayed + showKebabMenu?: boolean; // Determines if the KebabMenuList should be displayed + kebabMenuItems?: { id: string; name: string }[]; // Items for the KebabMenuList + defaultOpen?: boolean; // Determines if the dropdown list should be open by default + listType?: string; // Type of list to display +} + +const DropDownList: React.FC = ({ + value = "Dropdown", + items = [], + showFocusIcon = false, + showAddIcon = true, + showKebabMenu = true, + kebabMenuItems = [ + { id: "Buildings", name: "Buildings" }, + { id: "Paths", name: "Paths" }, + { id: "Zones", name: "Zones" }, + ], + defaultOpen = false, + listType = "default", +}) => { + const [isOpen, setIsOpen] = useState(defaultOpen); + + const handleToggle = () => { + setIsOpen((prev) => !prev); // Toggle the state + }; + + return ( +
+
+
+ {value} +
+
+ {showFocusIcon && ( +
+ +
+ )} + {showAddIcon && ( +
+ +
+ )} + {showKebabMenu && ( +
+ +
+ )} +
+ +
+
+
+ {isOpen && ( +
+ {listType === "default" && } + {listType === "outline" && ( + <> + + + + + )} +
+ )} +
+ ); +}; + +export default DropDownList; diff --git a/app/src/components/ui/list/KebebMenuList.tsx b/app/src/components/ui/list/KebebMenuList.tsx new file mode 100644 index 0000000..0e45ed0 --- /dev/null +++ b/app/src/components/ui/list/KebebMenuList.tsx @@ -0,0 +1,45 @@ +import React, { useState } from "react"; +import { KebebIcon } from "../../icons/ExportCommonIcons"; + +interface KebabMenuListProps { + items: string[]; // Array of menu items + onSelect?: (item: string) => void; // Callback when a menu item is selected +} + +const KebabMenuList: React.FC = ({ items, onSelect }) => { + const [isOpen, setIsOpen] = useState(false); + + const handleToggle = () => { + setIsOpen((prev) => !prev); + }; + + const handleItemClick = (item: string) => { + if (onSelect) { + onSelect(item); + } + setIsOpen(false); // Close menu after selection + }; + + return ( +
+
+ +
+ {isOpen && ( +
+ {items.map((item, index) => ( +
handleItemClick(item)} + > + {item} +
+ ))} +
+ )} +
+ ); +}; + +export default KebabMenuList; diff --git a/app/src/components/ui/list/KebebMenuListMultiSelect.tsx b/app/src/components/ui/list/KebebMenuListMultiSelect.tsx new file mode 100644 index 0000000..5f28e7f --- /dev/null +++ b/app/src/components/ui/list/KebebMenuListMultiSelect.tsx @@ -0,0 +1,82 @@ +import React, { useEffect, useRef, useState } from "react"; +import { KebebIcon, TickIcon } from "../../icons/ExportCommonIcons"; + +interface KebabMenuListMultiSelectProps { + items: { id: string; name: string }[]; // Array of menu items with id and name + onSelectionChange?: (selectedItems: string[]) => void; // Callback for selected items +} + +const KebabMenuListMultiSelect: React.FC = ({ + items, + onSelectionChange, +}) => { + const [isOpen, setIsOpen] = useState(false); + const [selectedItems, setSelectedItems] = useState([]); + const menuRef = useRef(null); // Ref to track the container + + const handleToggle = () => { + setIsOpen((prev) => !prev); + }; + + const handleItemToggle = (id: string) => { + setSelectedItems((prevSelected) => { + const isAlreadySelected = prevSelected.includes(id); + const updatedSelection = isAlreadySelected + ? prevSelected.filter((item) => item !== id) // Deselect if already selected + : [...prevSelected, id]; // Add to selection if not selected + + if (onSelectionChange) { + onSelectionChange(updatedSelection); + } + + return updatedSelection; + }); + }; + + // Close menu if clicked outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + + return ( +
+
+ +
+ {isOpen && ( +
+ {items.map((item) => ( +
handleItemToggle(item.id)} + > + handleItemToggle(item.id)} + /> +
+ {selectedItems.includes(item.id) && } +
+ {item.name} +
+ ))} +
+ )} +
+ ); +}; + +export default KebabMenuListMultiSelect; diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx new file mode 100644 index 0000000..1bf326f --- /dev/null +++ b/app/src/components/ui/list/List.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import RenameInput from "../inputs/RenameInput"; +import { EyeIcon, LockIcon, RmoveIcon } from "../../icons/ExportCommonIcons"; + +interface ListProps { + items?: { id: string; name: string }[]; // Optional array of items to render + placeholder?: string; // Optional placeholder text +} + +const List: React.FC = ({ items = [] }) => { + return ( + <> + {items.length > 0 ? ( +
    + {items.map((item, index) => ( +
  • +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
  • + ))} +
+ ) : ( +
+
No items to display
+
+ )} + + ); +}; + +export default List; diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index aadbf9b..6e0b37b 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -1,12 +1,14 @@ import React from 'react'; import ModuleToggle from '../components/ui/ModuleToggle'; import SideBarLeft from '../components/layout/sidebarLeft/SideBarLeft'; +import SideBarRight from '../components/layout/sidebarRight/SideBarRight'; const Project: React.FC = () => { return (
+
); }; diff --git a/app/src/store/useUIToggleStore.ts b/app/src/store/useUIToggleStore.ts new file mode 100644 index 0000000..a508b38 --- /dev/null +++ b/app/src/store/useUIToggleStore.ts @@ -0,0 +1,13 @@ +import { create } from "zustand"; + +interface ToggleState { + toggleUI: boolean; // State to track UI toggle + setToggleUI: (value: boolean) => void; // Action to update toggleUI +} + +const useToggleStore = create((set) => ({ + toggleUI: true, // Initial state + setToggleUI: (value: boolean) => set({ toggleUI: value }), // Update the state +})); + +export default useToggleStore; diff --git a/app/src/styles/base/base.scss b/app/src/styles/base/base.scss index c0788b2..d742fa5 100644 --- a/app/src/styles/base/base.scss +++ b/app/src/styles/base/base.scss @@ -40,6 +40,7 @@ --input-text-color: #{$input-text-color-dark}; // Input field text color // Accent and Highlight colors + --primary-color: #{$highlight-accent-color-dark}; --accent-color: #{$accent-color-dark}; // Primary accent color for dark theme --highlight-accent-color: #{$highlight-accent-color-dark}; // Highlight color for dark theme @@ -93,3 +94,28 @@ body { --font-weight-medium: #{$medium-weight}; // Medium font weight --font-weight-bold: #{$bold-weight}; // Bold font weight } + +/* Apply custom scrollbar styles globally */ +::-webkit-scrollbar { + width: 8px; /* Width of the scrollbar */ + height: 8px; /* Height for horizontal scrollbars */ +} + +::-webkit-scrollbar-track { + background: transparent; /* Background of the scrollbar track */ + border-radius: 4px; /* Rounded corners */ +} + +::-webkit-scrollbar-thumb { + background: var(--accent-color); /* Scrollbar handle color */ + border-radius: 4px; /* Rounded corners */ + border: 2px solid #f4f4f4; /* Padding around the scrollbar handle */ +} + +::-webkit-scrollbar-thumb:hover { + background: var(--accent-color); /* Handle color on hover */ +} + +::-webkit-scrollbar-corner { + background: transparent; /* Remove corner styling for scrollable containers */ +} diff --git a/app/src/styles/base/reset.scss b/app/src/styles/base/reset.scss index b0d8fff..96ff717 100644 --- a/app/src/styles/base/reset.scss +++ b/app/src/styles/base/reset.scss @@ -3,4 +3,5 @@ padding: 0; box-sizing: border-box; user-select: none; + font-size: var(--font-size-regular); } diff --git a/app/src/styles/components/input.scss b/app/src/styles/components/input.scss index e69de29..2186efe 100644 --- a/app/src/styles/components/input.scss +++ b/app/src/styles/components/input.scss @@ -0,0 +1,151 @@ +@use "../abstracts/variables" as *; +@use "../abstracts/mixins" as *; + +.input-value { + color: var(--input-text-color); + font-size: var(--font-size-regular); + font-weight: var(--font-weight-regular); + display: block; + width: 100%; + height: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.rename-input { + width: 100%; + color: var(--input-text-color); + font-size: var(--font-size-regular); + font-weight: var(--font-weight-regular); + border: 1px solid var(--accent-color); + outline: none; + border-radius: #{$border-radius-small}; + line-height: 26px; + padding: 0 8px; +} + +.toggle-header-container { + @include flex-center; + padding: 6px 12px; + .toggle-header-item { + width: 100%; + text-align: center; + padding: 4px 12px; + border-radius: #{$border-radius-large}; + } + .active { + background-color: var(--accent-color); + color: var(--primary-color); + } +} + +.search-wrapper { + position: sticky; + top: 0; + padding: 8px 10px; + background: var(--background-color); + z-index: 1; + .search-container { + @include flex-center; + width: 100%; + border-radius: #{$border-radius-small}; + background-color: var(--background-color); + padding: 6px 2px; + position: relative; + border: 1px solid var(--border-color); + + .icon-container { + @include flex-center; + padding: 0 8px; + position: absolute; + width: fit-content; + left: 0; + } + + .search-input { + width: 100%; + color: var(--input-text-color); + font-size: var(--font-size-regular); + font-weight: var(--font-weight-regular); + border: none; + outline: none; + background-color: transparent; + padding-left: 36px; + } + + .clear-button { + @include flex-center; + position: absolute; + right: 4px; + width: 24px; + height: 24px; + border: none; + cursor: pointer; + background-color: transparent; + &:hover { + background-color: var(--highlight-accent-color); + } + } + } + .active { + border: 1px solid var(--accent-color); + } +} + +.kebab-menu-container { + position: relative; + @include flex-center; + .kebab-icon { + @include flex-center; + } + .menu-list { + position: absolute; + left: 10px; + top: 12px; + background-color: var(--background-color); + border-radius: #{$border-radius-small}; + box-shadow: var(--box-shadow-medium); + z-index: 1; + padding: 8px 4px; + width: 170px; + .menu-item { + margin: 2px 0; + padding: 2px 4px; + cursor: pointer; + border-radius: #{$border-radius-small}; + display: flex; + gap: 2px; + &:hover { + background-color: var(--background-color-secondary); + } + .icon-container { + @include flex-center; + height: 18px; + width: 18px; + path { + stroke: var(--accent-color); + } + } + } + .selected { + background-color: var(--highlight-accent-color); + color: var(--accent-color); + &:hover { + background-color: var(--highlight-accent-color); + } + } + input { + display: none; + } + } +} + +.project-dropdowm-container { + position: relative; + height: 32px; + .project-name{ + line-height: 32px; + height: 100%; + } +} diff --git a/app/src/styles/components/lists.scss b/app/src/styles/components/lists.scss new file mode 100644 index 0000000..a9b0145 --- /dev/null +++ b/app/src/styles/components/lists.scss @@ -0,0 +1,55 @@ +@use "../abstracts/variables" as *; +@use "../abstracts/mixins" as *; + +.dropdown-list-container { + border-bottom: 1px solid var(--border-color); + &:last-child { + border: none; + } + .head { + @include flex-space-between; + padding: 6px 12px; + .options { + @include flex-center; + gap: 6px; + .option { + @include flex-center; + cursor: pointer; + transition: all 0.2s; + } + } + } +} + +.list-wrapper { + .no-item { + padding: 12px; + } + .list-container { + padding: 2px; + .list-item { + @include flex-space-between; + width: 100%; + text-align: center; + padding: 4px 12px; + border-radius: #{$border-radius-large}; + .value { + width: 100%; + text-align: start; + max-width: 180px; + } + .options-container { + @include flex-center; + gap: 6px; + .option { + @include flex-center; + cursor: pointer; + } + } + } + .active { + background-color: var(--highlight-accent-color); + color: var(--primary-color); + } + } +} diff --git a/app/src/styles/layout/grid.scss b/app/src/styles/layout/grid.scss deleted file mode 100644 index 5f9d4c5..0000000 --- a/app/src/styles/layout/grid.scss +++ /dev/null @@ -1,10 +0,0 @@ -@use "../abstracts/mixins" as mixins; - -.flex { - @include mixins.flex-center; - gap: 14px; -} -.flex-sb { - @include mixins.flex-space-between; - gap: 4px; -} diff --git a/app/src/styles/layout/header.scss b/app/src/styles/layout/header.scss deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss new file mode 100644 index 0000000..49ce93b --- /dev/null +++ b/app/src/styles/layout/sidebar.scss @@ -0,0 +1,249 @@ +@use "../abstracts/variables" as *; +@use "../abstracts/mixins" as *; + +.sidebar-left-wrapper { + width: 270px; + position: fixed; + top: 32px; + left: 8px; + background-color: var(--background-color); + border-radius: #{$border-radius-extra-large}; + box-shadow: #{$box-shadow-medium}; + .header-container { + @include flex-space-between; + padding: 10px; + width: 100%; + .header-content { + @include flex-center; + width: calc(100% - 34px); + .logo-container { + @include flex-center; + } + .header-title { + padding: 0 8px; + width: 100%; + max-width: calc(100% - 32px); + .input-value { + color: var(--accent-color); + } + } + } + .toggle-sidebar-ui-button { + @include flex-center; + cursor: pointer; + height: 32px; + width: 32px; + min-height: 32px; + min-width: 32px; + border-radius: #{$border-radius-small}; + &:hover { + background-color: var(--background-color-secondary); + } + } + .active { + background-color: var(--background-color-secondary); + outline: 1px solid var(--accent-color); + outline-offset: -1px; + } + } + .sidebar-left-container { + min-height: 50vh; + padding-bottom: 12px; + position: relative; + display: flex; + flex-direction: column; + .sidebar-left-content-container { + border-bottom: 1px solid var(--border-color); + // flex: 1; + height: calc(100% - 36px); + position: relative; + overflow: auto; + } + .outline-container { + height: 100%; + .outline-content-container { + position: relative; + height: 100%; + overflow: auto; + max-height: 60vh; + } + } + } +} + +.sidebar-right-wrapper { + width: 320px; + position: fixed; + top: 32px; + right: 8px; + background-color: var(--background-color); + border-radius: #{$border-radius-extra-large}; + box-shadow: #{$box-shadow-medium}; + .header-container { + @include flex-space-between; + padding: 10px; + width: 100%; + gap: 12px; + height: 52px; + .options-container { + @include flex-center; + gap: 8px; + .share-button { + padding: 4px 12px; + color: var(--primary-color); + background-color: var(--accent-color); + font-weight: var(--font-weight-regular); + border-radius: #{$border-radius-medium}; + cursor: pointer; + } + .app-docker-button { + @include flex-center; + } + } + .split { + height: 20px; + width: 2px; + background: var(--background-color-secondary); + } + .users-container { + width: 100%; + @include flex-space-between; + .user-profile { + @include flex-center; + height: 26px; + width: 26px; + min-height: 26px; + min-width: 26px; + border-radius: 50%; + font-weight: var(--font-weight-bold); + color: white; + } + .guest-users-container { + display: flex; + .other-guest { + @include flex-center; + height: 26px; + width: 26px; + min-height: 26px; + min-width: 26px; + border-radius: 50%; + background: var(--highlight-accent-color); + font-weight: var(--font-weight-bold); + color: var(--accent-color); + outline: 1px solid var(--accent-color); + outline-offset: -1px; + } + } + .user-profile-container { + display: flex; + .user-organnization { + height: 100%; + max-width: 52px; + img { + height: 100%; + width: 100%; + object-fit: cover; + } + } + } + } + } + .sidebar-actions-container { + position: absolute; + left: -40px; + .sidebar-action-list { + margin-bottom: 12px; + @include flex-center; + height: 34px; + width: 34px; + border-radius: #{$border-radius-circle}; + background: var(--primary-color); + box-shadow: #{$box-shadow-medium}; + } + .active { + background: var(--accent-color); + } + } + .sidebar-right-container { + min-height: 50vh; + padding-bottom: 12px; + position: relative; + display: flex; + flex-direction: column; + .sidebar-right-content-container { + border-bottom: 1px solid var(--border-color); + // flex: 1; + height: calc(100% - 36px); + position: relative; + overflow: auto; + } + } +} + +.machine-mechanics-container { + .header { + @include flex-space-between; + padding: 6px 12px; + .add-button { + @include flex-center; + padding: 2px 4px; + background: var(--accent-color); + color: var(--primary-color); + border-radius: #{$border-radius-small}; + cursor: pointer; + path { + stroke: var(--primary-color); + } + } + } + .lists-main-container { + margin: 2px 8px; + width: calc(100% - 16px); + background: var(--background-color-secondary); + border-radius: #{$border-radius-small}; + .list-container { + min-height: 120px; + padding: 4px; + .list-item { + @include flex-space-between; + padding: 4px 12px; + width: 100%; + margin: 2px 0; + border-radius: #{$border-radius-small}; + } + .active { + background: var(--accent-color); + .value { + color: var(--primary-color); + } + path { + stroke: var(--primary-color); + } + } + .remove-button { + @include flex-center; + height: 12px; + width: 12px; + cursor: pointer; + } + } + .resize-icon { + @include flex-center; + padding: 4px; + cursor: grab; + &:active { + cursor: grabbing; + } + } + } + .selected-properties-container { + padding: 12px; + } + .footer { + @include flex-center; + justify-content: flex-start; + gap: 4px; + padding: 12px; + font-size: var(--font-size-tiny); + } +} diff --git a/app/src/styles/main.scss b/app/src/styles/main.scss index 472e2a0..2c9b7df 100644 --- a/app/src/styles/main.scss +++ b/app/src/styles/main.scss @@ -11,18 +11,16 @@ // components @use 'components/button'; -@use 'components/form.scss'; +@use 'components/form'; @use 'components/input'; -@use 'components/layouts.scss'; -@use 'components/moduleToggle.scss'; -@use 'components/templates.scss'; -@use 'components/tools.scss'; +@use 'components/layouts'; +@use 'components/lists'; +@use 'components/moduleToggle'; +@use 'components/templates'; +@use 'components/tools'; // layout -@use 'layout/grid'; -@use 'layout/header'; -@use 'layout/aside'; -@use 'layout/footer'; +@use 'layout/sidebar'; // pages @use 'pages/home';