Initial Commit

This commit is contained in:
2025-09-07 01:46:37 -04:00
commit 66986cca51
272 changed files with 15331 additions and 0 deletions

View File

@@ -0,0 +1,206 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
import React, {useState, useEffect, useRef} from 'react';
import {IChannel} from 'interfaces';
import {Label} from 'components/label';
import {DropdownContainer, DropdownInput, DropdownMenu, DropdownMenuItem} from './style';
const keys = ['ArrowDown', 'ArrowUp', 'Enter', 'Escape'];
interface IDropdown {
itemKey: string;
searchTerm?: string;
onSelect: (key: string) => void;
items: IChannel[];
label: string;
name: string;
className?: string;
}
const maxNumOfItemsShown = 30;
export const Dropdown = (props: IDropdown): React.JSX.Element => {
const {itemKey, searchTerm, onSelect, items, label, name, className} = props;
const [showDropdownMenu, setShowDropdownMenu] = useState(false);
const [input, setInput] = useState(searchTerm || '');
const [filteredItems, setFilteredItems] = useState(items);
const [selectedIndex, setSelectedIndex] = useState(0);
const allItemsRef = useRef<any[]>([]);
const parentElementRef = useRef<HTMLDivElement | null>(null);
const scrollSelectedItemInView = (index: number) => {
if (!parentElementRef.current) {
return;
}
const dropdownMenu = parentElementRef.current;
const menuItems = allItemsRef.current;
if (!menuItems) {
return;
}
const menuItem = menuItems[index];
if (!menuItem) {
return;
}
const isOutOfUpperView = menuItem.offsetTop < dropdownMenu.scrollTop;
const isOutOfLowerView = menuItem.offsetTop + menuItem.clientHeight > dropdownMenu.scrollTop + dropdownMenu.clientHeight;
if (isOutOfUpperView) {
dropdownMenu.scrollTop = menuItem.offsetTop;
} else if (isOutOfLowerView) {
dropdownMenu.scrollTop = menuItem.offsetTop + menuItem.clientHeight - dropdownMenu.clientHeight;
}
};
const setSelectInput = (value: string) => {
setInput(value);
onSelect(value);
};
const handleClickOutside = (event: MouseEvent) => {
if (!parentElementRef.current || parentElementRef.current.contains(event.target as Node)) {
return;
}
if (!filteredItems.length) {
setSelectInput('');
}
setShowDropdownMenu(false);
onSelect(input);
};
useEffect(() => {
scrollSelectedItemInView(selectedIndex);
}, [selectedIndex]);
useEffect(() => {
document.addEventListener('click', handleClickOutside, true);
return () => {
document.removeEventListener('click', handleClickOutside, true);
};
});
useEffect(() => {
const filteredItems = items.filter(item => {
if ((item as any)[itemKey].toLowerCase().indexOf(input.toLowerCase()) > -1) {
return true;
}
return false;
});
if (filteredItems.length > 0) {
setSelectedIndex(0);
}
setFilteredItems(filteredItems);
}, [input, items, itemKey]);
const selectItemInFocusBy = (offset: number) => {
const lastIndex = filteredItems.length - 1;
const nextIndex = selectedIndex + offset;
if (nextIndex > lastIndex) {
setSelectedIndex(0);
} else if (nextIndex < 0) {
setSelectedIndex(lastIndex);
} else {
setSelectedIndex(nextIndex);
}
};
const handleOnKeyDown = (event: React.KeyboardEvent) => {
if (keys.indexOf(event.key) === -1) {
return;
}
const [arrDown, arrUp, enter, escape] = keys;
const moves = {
[arrDown]: 1,
[arrUp]: -1
};
const move = moves[event.key];
if (move !== undefined) {
event.preventDefault();
selectItemInFocusBy(move);
}
if (event.key === enter) {
if (filteredItems[selectedIndex]) {
setSelectInput((filteredItems[selectedIndex] as any)[itemKey]);
setShowDropdownMenu(false);
}
}
if (event.key === escape) {
event.preventDefault();
setShowDropdownMenu(false);
}
};
const selectItem = (index: number) => {
setSelectInput((filteredItems[index] as any)[itemKey]);
setSelectedIndex(index);
setShowDropdownMenu(false);
};
const generateMenuOptions = () => {
return filteredItems.length ? (
filteredItems.slice(0, maxNumOfItemsShown).map((item, index) => {
return (
<DropdownMenuItem
ref={ref => {
allItemsRef.current[index] = ref;
}}
selected={selectedIndex === index}
key={`dropdown-menu-item-${index}`}
onClick={() => selectItem(index)}>
{(item as any)[itemKey]}
</DropdownMenuItem>
);
})
) : (
<DropdownMenuItem disabled={true}>No results found</DropdownMenuItem>
);
};
const handleInput = (event: React.ChangeEvent<HTMLInputElement>) => {
const {target} = event;
setInput(target.value);
if (!showDropdownMenu) {
setShowDropdownMenu(true);
}
};
const toggleDropdownMenu = () => setShowDropdownMenu(!showDropdownMenu);
return (
<DropdownContainer>
<Label htmlFor="autocomplete" text={label} />
<DropdownInput
onKeyDown={handleOnKeyDown}
showMenu={showDropdownMenu}
autoComplete="off"
name={name}
onChange={handleInput}
value={input}
onClick={toggleDropdownMenu}
className={className}
/>
{showDropdownMenu && (
<DropdownMenu ref={parentElementRef} className="testId-generatedMenuOptions">
{generateMenuOptions()}
</DropdownMenu>
)}
</DropdownContainer>
);
};

View File

@@ -0,0 +1,61 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
import * as styled from 'styled-components';
import Input from 'components/forms/Input';
import Theme from 'theme';
const {spacing, colors, typography, primaryBorderRadius} = Theme;
export const DropdownContainer = styled.default.div`
width: 100%;
position: relative;
margin: ${spacing.xSmall} 0;
`;
export const DropdownInput = styled.default(Input)<{showMenu?: boolean}>`
${({showMenu}) =>
showMenu &&
styled.css`
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
border-bottom-width: 0;
`}
`;
export const DropdownMenu = styled.default.div`
width: 100%;
overflow-x: hidden;
overflow-y: auto;
top: calc(100% - 4px);
position: absolute;
height: auto;
max-height: 210px;
border: 1px solid ${colors.gray400};
border-top-width: 0;
z-index: 4;
background-color: ${colors.white};
border-bottom-left-radius: ${primaryBorderRadius};
border-bottom-right-radius: ${primaryBorderRadius};
`;
export const DropdownMenuItem = styled.default.div<{
active?: boolean;
disabled?: boolean;
selected?: boolean;
}>`
line-height: ${spacing.large};
padding: ${spacing.xsmall} ${spacing.medium};
font-size: ${typography.fontSizeS};
word-wrap: break-word;
${({active, selected, disabled}) => styled.css`
background-color: ${active || selected ? colors.gray300 : 'transparent'};
font-weight: ${selected ? 'bold' : 'normal'};
color: ${disabled ? colors.gray500 : colors.gray900}
:hover{
background-color: ${disabled ? colors.white : colors.gray300};
cursor: ${disabled ? 'text' : 'pointer'};
}
`}
`;