first commit
This commit is contained in:
35
app/src/components/ui/inputs/EyeDropInput.tsx
Normal file
35
app/src/components/ui/inputs/EyeDropInput.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { EyeDroperIcon } from "../../icons/ExportCommonIcons";
|
||||
|
||||
interface EyeDropInputProps {
|
||||
label: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
|
||||
const EyeDropInput: React.FC<EyeDropInputProps> = ({
|
||||
label = "Object",
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
const handleEyeDropClick = () => {
|
||||
const simulatedValue = "picked_value"; // Replace with actual eye dropper logic
|
||||
onChange(simulatedValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="eye-dropper-input-container">
|
||||
<div className="label">{label}</div>
|
||||
<div className="input-container">
|
||||
<input disabled type="text" value={value}/>
|
||||
{/* <input type="text" value={activeValue ?? "null"} disabled /> */}
|
||||
{/* <input type="button" value="Clear" onClick={handleEyeDropClick}/> */}
|
||||
<button id="eye-picker" className="eye-picker-button" onClick={handleEyeDropClick}>
|
||||
<EyeDroperIcon isActive={false} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EyeDropInput;
|
||||
93
app/src/components/ui/inputs/InputRange.tsx
Normal file
93
app/src/components/ui/inputs/InputRange.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import * as CONSTANTS from "../../../types/world/worldConstants";
|
||||
interface InputToggleProps {
|
||||
label: string; // Represents the toggle state (on/off)
|
||||
min?: number;
|
||||
max?: number;
|
||||
onClick?: () => void; // Function to handle toggle clicks
|
||||
onChange?: (value: number) => void; // Function to handle toggle clicks
|
||||
disabled?: boolean;
|
||||
value?: number;
|
||||
onPointerUp?: (value: number) => void;
|
||||
}
|
||||
|
||||
const InputRange: React.FC<InputToggleProps> = ({
|
||||
label,
|
||||
onClick,
|
||||
onChange,
|
||||
min,
|
||||
max,
|
||||
disabled,
|
||||
value,
|
||||
onPointerUp,
|
||||
}) => {
|
||||
const [rangeValue, setRangeValue] = useState<number>(value ? value : 5);
|
||||
|
||||
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const newValue = parseInt(e.target.value); // Parse the value to an integer
|
||||
|
||||
setRangeValue(newValue); // Update the local state
|
||||
|
||||
if (onChange) {
|
||||
onChange(newValue); // Call the onChange function if it exists
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
value && setRangeValue(value);
|
||||
}, [value]);
|
||||
function handlePointerUp(e: React.PointerEvent<HTMLInputElement>) {
|
||||
const newValue = parseInt(e.currentTarget.value, 10); // Parse value correctly
|
||||
|
||||
if (onPointerUp) {
|
||||
onPointerUp(newValue); // Call the callback function if it exists
|
||||
}
|
||||
}
|
||||
function handlekey(e: React.KeyboardEvent<HTMLInputElement>) {
|
||||
const newValue = parseInt(e.currentTarget.value, 10); // Parse value correctly
|
||||
|
||||
if (onPointerUp) {
|
||||
onPointerUp(newValue); // Call the callback function if it exists
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="input-range-container">
|
||||
<label
|
||||
htmlFor={`range-input ${value}`}
|
||||
className="label"
|
||||
onClick={onClick}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
<div className="input-container">
|
||||
<input
|
||||
id={`range-input ${value}`}
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
onChange={handleChange}
|
||||
disabled={disabled}
|
||||
value={rangeValue}
|
||||
onPointerUp={handlePointerUp}
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
min={min}
|
||||
className="input-value"
|
||||
max={max}
|
||||
value={rangeValue}
|
||||
onChange={handleChange}
|
||||
disabled={disabled}
|
||||
onKeyUp={(e) => {
|
||||
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
||||
console.log("e.key: ", e.key);
|
||||
handlekey(e);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputRange;
|
||||
55
app/src/components/ui/inputs/InputToggle.tsx
Normal file
55
app/src/components/ui/inputs/InputToggle.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import React from "react";
|
||||
|
||||
interface InputToggleProps {
|
||||
label: string; // Represents the toggle state (on/off)
|
||||
onClick?: () => void; // Function to handle toggle clicks
|
||||
value?: boolean;
|
||||
inputKey: string;
|
||||
}
|
||||
|
||||
// Update InputToggle.tsx to be fully controlled
|
||||
const InputToggle: React.FC<InputToggleProps> = ({
|
||||
label,
|
||||
onClick,
|
||||
value = false,
|
||||
inputKey,
|
||||
}) => {
|
||||
// Remove internal state and use the value prop directly
|
||||
function handleOnClick() {
|
||||
if (onClick) onClick();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="input-toggle-container">
|
||||
<label htmlFor={`toogle-input-${inputKey}`} className="label">
|
||||
{label}
|
||||
</label>
|
||||
<button
|
||||
id="check-box"
|
||||
className={"check-box"}
|
||||
onClick={handleOnClick}
|
||||
style={{
|
||||
background: value ? "var(--background-color-accent)" : "",
|
||||
outline: value ? "" : "1px solid var(--border-color)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="check-box-style"
|
||||
style={{
|
||||
left: value ? "16px" : "2px",
|
||||
background: value ? "" : "var(--text-disabled)",
|
||||
}}
|
||||
></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
name=""
|
||||
id={`toogle-input-${inputKey}`}
|
||||
checked={value}
|
||||
readOnly
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputToggle;
|
||||
91
app/src/components/ui/inputs/InputWithDropDown.tsx
Normal file
91
app/src/components/ui/inputs/InputWithDropDown.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import React, { useState } from "react";
|
||||
import RenameInput from "./RenameInput";
|
||||
|
||||
type InputWithDropDownProps = {
|
||||
label: string;
|
||||
value: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
defaultValue?: string;
|
||||
options?: string[]; // Array of dropdown options
|
||||
activeOption?: string; // The currently active dropdown option
|
||||
onClick?: () => void;
|
||||
onChange: (newValue: string) => void;
|
||||
editableLabel?: boolean;
|
||||
placeholder?: string; // New placeholder prop
|
||||
};
|
||||
|
||||
const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
|
||||
label,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
defaultValue,
|
||||
options,
|
||||
activeOption,
|
||||
onClick,
|
||||
onChange,
|
||||
editableLabel = false,
|
||||
placeholder = "Inherit", // Default empty placeholder
|
||||
}) => {
|
||||
const separatedWords = label
|
||||
.split(/(?=[A-Z])/)
|
||||
.map((word) => word.trim())
|
||||
.toString();
|
||||
|
||||
const [openDropdown, setOpenDropdown] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="value-field-container">
|
||||
{editableLabel ? (
|
||||
<RenameInput value={label} />
|
||||
) : (
|
||||
<label htmlFor={separatedWords} className="label">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<div className="input default" id={separatedWords}>
|
||||
<input
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
type="number"
|
||||
defaultValue={value}
|
||||
// value={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
placeholder={placeholder} // Added placeholder prop
|
||||
/>
|
||||
|
||||
{activeOption && (
|
||||
<div
|
||||
className="dropdown"
|
||||
onClick={() => {
|
||||
setOpenDropdown(true);
|
||||
}}
|
||||
>
|
||||
<div className="active-option">{activeOption}</div>
|
||||
{options && openDropdown && (
|
||||
<div className="dropdown-options-list">
|
||||
{options.map((option, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={"dropdown-option"}
|
||||
onClick={onClick}
|
||||
>
|
||||
{option}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputWithDropDown;
|
||||
31
app/src/components/ui/inputs/LabledButton.tsx
Normal file
31
app/src/components/ui/inputs/LabledButton.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from "react";
|
||||
|
||||
interface LabeledButtonProps {
|
||||
label: string; // Label for the button
|
||||
onClick?: () => void; // Function to call when the button is clicked
|
||||
disabled?: boolean; // Optional prop to disable the button
|
||||
value?: string;
|
||||
}
|
||||
|
||||
const LabeledButton: React.FC<LabeledButtonProps> = ({
|
||||
label,
|
||||
onClick,
|
||||
disabled = false,
|
||||
value = "Click here",
|
||||
}) => {
|
||||
return (
|
||||
<div className="labeled-button-container">
|
||||
<div className="label">{label}</div>
|
||||
<button
|
||||
id="label-button"
|
||||
className="button"
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{value}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LabeledButton;
|
||||
49
app/src/components/ui/inputs/LabledDropdown.tsx
Normal file
49
app/src/components/ui/inputs/LabledDropdown.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import RegularDropDown from "./RegularDropDown";
|
||||
|
||||
type LabledDropdownProps = {
|
||||
defaultOption: string; // Initial active option
|
||||
options: string[]; // Array of dropdown options
|
||||
label?: string; // Customizable label text
|
||||
onSelect?: (option: string) => void; // Callback when option is selected
|
||||
className?: string; // Additional className for styling
|
||||
disabled?: boolean; // Disable dropdown
|
||||
search?: boolean; // Enable/disable search functionality
|
||||
};
|
||||
|
||||
const LabledDropdown: React.FC<LabledDropdownProps> = ({
|
||||
defaultOption,
|
||||
options,
|
||||
label = "Type",
|
||||
onSelect,
|
||||
className = "",
|
||||
search = false
|
||||
}) => {
|
||||
const [activeOption, setActiveOption] = useState(defaultOption);
|
||||
|
||||
// Update active option if defaultOption changes
|
||||
useEffect(() => {
|
||||
setActiveOption(defaultOption);
|
||||
}, [defaultOption]);
|
||||
|
||||
const handleSelect = (option: string) => {
|
||||
setActiveOption(option);
|
||||
if (onSelect) {
|
||||
onSelect(option);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`value-field-container ${className}`}>
|
||||
<div className="label">{label}</div>
|
||||
<RegularDropDown
|
||||
header={activeOption}
|
||||
options={options}
|
||||
onSelect={handleSelect}
|
||||
search={search}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LabledDropdown;
|
||||
74
app/src/components/ui/inputs/MultiEmailInvite.tsx
Normal file
74
app/src/components/ui/inputs/MultiEmailInvite.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
const MultiEmailInvite: React.FC = () => {
|
||||
const [emails, setEmails] = useState<string[]>([]);
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
|
||||
const handleAddEmail = () => {
|
||||
const trimmedEmail = inputValue.trim();
|
||||
|
||||
// Validate email
|
||||
if (!trimmedEmail || !validateEmail(trimmedEmail)) {
|
||||
alert("Please enter a valid email address.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
if (emails.includes(trimmedEmail)) {
|
||||
alert("This email has already been added.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Add email to the list
|
||||
setEmails([...emails, trimmedEmail]);
|
||||
setInputValue(""); // Clear the input field after adding
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" || e.key === ",") {
|
||||
e.preventDefault();
|
||||
handleAddEmail();
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveEmail = (emailToRemove: string) => {
|
||||
setEmails(emails.filter((email) => email !== emailToRemove));
|
||||
};
|
||||
|
||||
const validateEmail = (email: string) => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
};
|
||||
|
||||
const [inputFocus, setInputFocus] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="multi-email-invite-input-container">
|
||||
<div className={`multi-email-invite-input${inputFocus ? " active" : ""}`}>
|
||||
{emails.map((email, index) => (
|
||||
<div key={index} className="entered-emails">
|
||||
{email}
|
||||
<span onClick={() => handleRemoveEmail(email)}>×</span>
|
||||
</div>
|
||||
))}
|
||||
<input
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onFocus={() => setInputFocus(true)}
|
||||
onBlur={() => setInputFocus(false)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Enter email and press Enter or comma to seperate"
|
||||
/>
|
||||
</div>
|
||||
<div onClick={handleAddEmail} className="invite-button">
|
||||
Invite
|
||||
</div>
|
||||
<div className="users-list-container">
|
||||
{/* list available users */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MultiEmailInvite;
|
||||
176
app/src/components/ui/inputs/MultiLevelDropDown.tsx
Normal file
176
app/src/components/ui/inputs/MultiLevelDropDown.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { ArrowIcon } from "../../icons/ExportCommonIcons";
|
||||
|
||||
// Dropdown Item Component
|
||||
const DropdownItem = ({
|
||||
label,
|
||||
onClick,
|
||||
disabled = false,
|
||||
}: {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
}) => (
|
||||
<div
|
||||
className={`dropdown-item ${disabled ? "disabled" : ""}`}
|
||||
onClick={() => {
|
||||
if (!disabled) onClick();
|
||||
}}
|
||||
style={{
|
||||
cursor: disabled ? "not-allowed" : "default",
|
||||
opacity: disabled ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
|
||||
// Nested Dropdown Component
|
||||
const NestedDropdown = ({
|
||||
label,
|
||||
fields,
|
||||
onSelect,
|
||||
disabledFields = [],
|
||||
}: {
|
||||
label: string;
|
||||
fields: string[];
|
||||
onSelect: (selectedData: { name: string; fields: string }) => void;
|
||||
disabledFields?: string[];
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="nested-dropdown">
|
||||
<div
|
||||
className={`dropdown-trigger ${open ? "open" : ""}`}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
{label}
|
||||
<div
|
||||
className="arrow-container"
|
||||
style={{ rotate: open ? "" : "-90deg" }}
|
||||
>
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</div>
|
||||
{open && (
|
||||
<div className="submenu">
|
||||
{fields.map((field) => {
|
||||
const isDisabled = disabledFields.includes(`${label}-${field}`);
|
||||
return (
|
||||
<DropdownItem
|
||||
key={field}
|
||||
label={field}
|
||||
onClick={() => onSelect({ name: label, fields: field })}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Props type for MultiLevelDropdown
|
||||
interface MultiLevelDropdownProps {
|
||||
data: Record<string, any>;
|
||||
onSelect: (selectedData: { name: string; fields: string }) => void;
|
||||
onUnselect: () => void;
|
||||
selectedValue?: { name: string; fields: string };
|
||||
allSelections?: Record<string, { name: string; fields: string }>;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
// Main Multi-Level Dropdown Component
|
||||
const MultiLevelDropdown = ({
|
||||
data,
|
||||
onSelect,
|
||||
onUnselect,
|
||||
selectedValue,
|
||||
allSelections = {},
|
||||
isLoading = false,
|
||||
}: MultiLevelDropdownProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Handle item selection
|
||||
const handleItemSelect = (selectedData: { name: string; fields: string }) => {
|
||||
onSelect(selectedData);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
// Handle unselect
|
||||
const handleItemUnselect = () => {
|
||||
onUnselect();
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
// Determine the display label
|
||||
const displayLabel = selectedValue
|
||||
? `${selectedValue.name} - ${selectedValue.fields}`
|
||||
: "Dropdown trigger";
|
||||
|
||||
// Build list of disabled selections
|
||||
const disabledFieldsList = Object.values(allSelections)
|
||||
.filter(
|
||||
(sel) =>
|
||||
!(
|
||||
sel.name === selectedValue?.name &&
|
||||
sel.fields === selectedValue?.fields
|
||||
)
|
||||
)
|
||||
.map((sel) => `${sel.name}-${sel.fields}`);
|
||||
|
||||
return (
|
||||
<div className="multi-level-dropdown" ref={dropdownRef}>
|
||||
<button
|
||||
id="multi-level-drop"
|
||||
className={`dropdown-button ${open ? "open" : ""}`}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
<div className="label">{displayLabel}</div>
|
||||
<span className="icon">▾</span>
|
||||
</button>
|
||||
{open && (
|
||||
<div className="dropdown-menu">
|
||||
<div className="dropdown-content">
|
||||
{isLoading ? (
|
||||
<div className="loading" />
|
||||
) : (
|
||||
<>
|
||||
<DropdownItem label="Unselect" onClick={handleItemUnselect} />
|
||||
{Object.entries(data).map(([key, value]) => (
|
||||
<NestedDropdown
|
||||
key={key}
|
||||
label={key}
|
||||
fields={Object.keys(value)}
|
||||
onSelect={handleItemSelect}
|
||||
disabledFields={disabledFieldsList}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MultiLevelDropdown;
|
||||
75
app/src/components/ui/inputs/PreviewSelectionWithUpload.tsx
Normal file
75
app/src/components/ui/inputs/PreviewSelectionWithUpload.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React, { useState } from "react";
|
||||
import { ArrowIcon } from "../../icons/ExportCommonIcons";
|
||||
import LabledDropdown from "./LabledDropdown";
|
||||
|
||||
interface PreviewSelectionWithUploadProps {
|
||||
preview?: boolean;
|
||||
upload?: boolean;
|
||||
label?: string;
|
||||
onSelect: (option: string) => void;
|
||||
defaultOption: string;
|
||||
options: string[];
|
||||
}
|
||||
|
||||
const PreviewSelectionWithUpload: React.FC<PreviewSelectionWithUploadProps> = ({
|
||||
preview = false,
|
||||
upload = false,
|
||||
onSelect,
|
||||
label,
|
||||
defaultOption,
|
||||
options,
|
||||
}) => {
|
||||
const [showPreview, setShowPreview] = useState(false);
|
||||
return (
|
||||
<div className="preview-selection-with-upload-wrapper">
|
||||
{preview && (
|
||||
<>
|
||||
<button
|
||||
id="preview-selection-button"
|
||||
className="input-header-container"
|
||||
onClick={() => setShowPreview(!showPreview)}
|
||||
>
|
||||
<div className="input-header">Preview</div>
|
||||
<div
|
||||
className="arrow-container"
|
||||
style={{ rotate: showPreview ? "0deg" : "90deg" }}
|
||||
>
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</button>
|
||||
{showPreview && (
|
||||
<div className="canvas-wrapper">
|
||||
<div className="canvas-container"></div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{upload && (
|
||||
<div className="asset-selection-container">
|
||||
<div className="upload-custom-asset-button">
|
||||
<div className="title">Upload Product</div>
|
||||
<input
|
||||
type="file"
|
||||
accept=".glb, .gltf"
|
||||
id="simulation-product-upload"
|
||||
/>
|
||||
<label
|
||||
className="upload-button"
|
||||
htmlFor="simulation-product-upload"
|
||||
>
|
||||
Upload here
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<LabledDropdown
|
||||
label={label}
|
||||
defaultOption={defaultOption}
|
||||
options={options}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PreviewSelectionWithUpload;
|
||||
127
app/src/components/ui/inputs/RegularDropDown.tsx
Normal file
127
app/src/components/ui/inputs/RegularDropDown.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
|
||||
interface DropdownProps {
|
||||
header: string;
|
||||
options: string[];
|
||||
onSelect: (option: string) => void;
|
||||
search?: boolean;
|
||||
onClick?: () => void;
|
||||
onChange?: () => void;
|
||||
}
|
||||
|
||||
const RegularDropDown: React.FC<DropdownProps> = ({
|
||||
header,
|
||||
options,
|
||||
onSelect,
|
||||
search = true,
|
||||
onClick,
|
||||
onChange,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState<string | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState(""); // State to store search term
|
||||
const [filteredOptions, setFilteredOptions] = useState<string[]>(options); // State for filtered options
|
||||
const dropdownRef = useRef<HTMLDivElement>(null); // Ref for the dropdown container
|
||||
|
||||
// Reset selectedOption when the dropdown closes
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setSelectedOption(null);
|
||||
setSearchTerm(""); // Clear the search term when the dropdown closes
|
||||
setFilteredOptions(options); // Reset filtered options when the dropdown closes
|
||||
}
|
||||
}, [isOpen, options]);
|
||||
|
||||
// Reset selectedOption when the header prop changes
|
||||
useEffect(() => {
|
||||
setSelectedOption(null);
|
||||
setSearchTerm(""); // Reset search term if header changes
|
||||
setFilteredOptions(options); // Reset options if header changes
|
||||
}, [header, options]);
|
||||
|
||||
// Close dropdown if clicked outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", handleClickOutside);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("click", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Toggle the dropdown
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen((prev) => !prev);
|
||||
};
|
||||
|
||||
// Handle option selection
|
||||
const handleOptionClick = (option: string) => {
|
||||
setSelectedOption(option);
|
||||
onSelect(option);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
// Handle search input change
|
||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const term = event.target.value;
|
||||
setSearchTerm(term);
|
||||
|
||||
// Filter options based on the search term
|
||||
const filtered = options.filter((option) =>
|
||||
option.toLowerCase().includes(term.toLowerCase())
|
||||
);
|
||||
setFilteredOptions(filtered);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="regularDropdown-container" ref={dropdownRef}>
|
||||
{/* Dropdown Header */}
|
||||
<div className="dropdown-header flex-sb" onClick={toggleDropdown}>
|
||||
<div className="key">{selectedOption || header}</div>
|
||||
<div className="icon">▾</div>
|
||||
</div>
|
||||
|
||||
{/* Dropdown Options */}
|
||||
{isOpen && (
|
||||
<div className="dropdown-options">
|
||||
{/* Search Bar */}
|
||||
{search && (
|
||||
<div className="dropdown-search">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Filtered Options */}
|
||||
{filteredOptions.length > 0 ? (
|
||||
filteredOptions.map((option, index) => (
|
||||
<div
|
||||
className="option"
|
||||
key={index}
|
||||
onClick={() => handleOptionClick(option)}
|
||||
>
|
||||
{option}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="no-options">No options found</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegularDropDown;
|
||||
80
app/src/components/ui/inputs/RenameInput.tsx
Normal file
80
app/src/components/ui/inputs/RenameInput.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
|
||||
// interface RenameInputProps {
|
||||
// value: string;
|
||||
// onRename?: (newText: string) => void;
|
||||
// }
|
||||
|
||||
interface RenameInputProps {
|
||||
value: string;
|
||||
onRename?: (newText: string) => void;
|
||||
checkDuplicate?: (name: string) => boolean;
|
||||
}
|
||||
|
||||
const RenameInput: React.FC<RenameInputProps> = ({ value, onRename, checkDuplicate }) => {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [text, setText] = useState(value);
|
||||
const [isDuplicate, setIsDuplicate] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setText(value);
|
||||
}, [value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (checkDuplicate) {
|
||||
setIsDuplicate(checkDuplicate(text));
|
||||
}
|
||||
}, [text, checkDuplicate]);
|
||||
|
||||
const handleDoubleClick = () => {
|
||||
setIsEditing(true);
|
||||
setTimeout(() => inputRef.current?.focus(), 0);
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
|
||||
if(isDuplicate) return
|
||||
setIsEditing(false);
|
||||
if (onRename && !isDuplicate) {
|
||||
onRename(text);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setText(e.target.value);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter" && !isDuplicate) {
|
||||
setIsEditing(false);
|
||||
if (onRename) {
|
||||
onRename(text);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isEditing ? (
|
||||
<>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={text}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
className={`rename-input ${isDuplicate ? "input-error" : ""}`}
|
||||
/>
|
||||
{/* {isDuplicate && <div className="error-msg">Name already exists!</div>} */}
|
||||
</>
|
||||
) : (
|
||||
<span onDoubleClick={handleDoubleClick} className="input-value">
|
||||
{text}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default RenameInput
|
||||
71
app/src/components/ui/inputs/Search.tsx
Normal file
71
app/src/components/ui/inputs/Search.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, { ChangeEvent, useState } from "react";
|
||||
import { CloseIcon, SearchIcon } from "../../icons/ExportCommonIcons";
|
||||
|
||||
interface SearchProps {
|
||||
value?: string; // The current value of the search input
|
||||
placeholder?: string; // Placeholder text for the input
|
||||
onChange: (value: string) => void; // Callback function to handle input changes
|
||||
}
|
||||
|
||||
const Search: React.FC<SearchProps> = ({
|
||||
value = "",
|
||||
placeholder = "Search",
|
||||
onChange,
|
||||
}) => {
|
||||
// State to track the input value and focus status
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
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 (
|
||||
<div className="search-wrapper">
|
||||
<div
|
||||
className={`search-container ${
|
||||
isFocused || inputValue ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="icon-container">
|
||||
<SearchIcon />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className="search-input"
|
||||
value={inputValue}
|
||||
placeholder={placeholder}
|
||||
onChange={handleInputChange}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
{inputValue && (
|
||||
<button
|
||||
id="clear-button"
|
||||
className="clear-button"
|
||||
onClick={handleClear}
|
||||
>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Search;
|
||||
31
app/src/components/ui/inputs/ToggleHeader.tsx
Normal file
31
app/src/components/ui/inputs/ToggleHeader.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from "react";
|
||||
|
||||
interface ToggleHeaderProps {
|
||||
options: string[]; // Array of strings representing the options
|
||||
activeOption: string; // The currently active option
|
||||
handleClick: (option: string) => void; // Function to handle click events
|
||||
}
|
||||
|
||||
const ToggleHeader: React.FC<ToggleHeaderProps> = ({
|
||||
options,
|
||||
activeOption,
|
||||
handleClick,
|
||||
}) => {
|
||||
return (
|
||||
<div className="toggle-header-container">
|
||||
{options.map((option, index) => (
|
||||
<button
|
||||
key={`${index}-${option}`}
|
||||
className={`toggle-header-item ${
|
||||
option === activeOption ? "active" : ""
|
||||
}`}
|
||||
onClick={() => handleClick(option)} // Call handleClick when an option is clicked
|
||||
>
|
||||
{option}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToggleHeader;
|
||||
Reference in New Issue
Block a user