Skip to content

Commit

Permalink
addable select box for selecting context
Browse files Browse the repository at this point in the history
  • Loading branch information
tarborlevan committed May 14, 2024
1 parent c79e61a commit 5c47480
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 6 deletions.
8 changes: 4 additions & 4 deletions src/assets/base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -194,18 +194,18 @@ li.tab {
flex-wrap: wrap;

.input-container {
width: 45%;
margin-right: 5%;
width: 47%;
margin-right: 1.5%;
margin-bottom: 1rem;

&:nth-child(2n) {
margin-left: 5%;
margin-left: 1.5%;
margin-right: 0;
}

input {
background-color: rgba(255, 255, 255, 0.9);
width: 100%;
width: calc(100% - 12px);
padding: 0 5px;
border: 1px solid #f2f2f2;
border-radius: 2px;
Expand Down
87 changes: 87 additions & 0 deletions src/popup/common-components/inputs/SearchableSelectBox.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
.selectBoxContainer {
width: 100%;
height: 3rem;

.selectBox {
background-color: rgba(255, 255, 255, 0.9);
width: 100%;
height: 100%;
border: 1px solid #f2f2f2;
border-radius: 2px;
font-size: 14px;
display: block;
line-height: 2.15;
margin: 0;
position: relative;

.selectedOption {
user-select: none;
padding: 5px;
cursor: pointer;

.placeholder {
color: gray;
}
}

ul.options {
max-height: 0;
position: absolute;
top: 3rem;
left: 0;
right: 0;
overflow: hidden;
transition: .2s ease-in;
margin: 0;
background: white;
box-shadow: 0 0 3px silver;
border-radius: 0 0 4px 4px;
z-index: 1;
display: flex;

li.option {
user-select: none;
padding: 5px;
cursor: pointer;

&:hover {
background: whitesmoke;
}
}

li.noResults {
user-select: none;
padding: 5px;
color: gray;
text-align: center;
}

.searchInputContainer {
input {
margin: 0;
}
}
}

}

&.open {
.selectBox {
ul.options {
max-height: 200px;
}
}
}

&.reversed {
.selectBox {

ul.options {
border-radius: 4px 4px 0 0;
top: unset;
bottom: 3rem;
flex-direction: column-reverse;
}
}
}
}
75 changes: 75 additions & 0 deletions src/popup/common-components/inputs/SearchableSelectBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { useEffect, useRef, useState } from 'react';
import styles from "./SearchableSelectBox.module.scss";
import { cx } from '@/popup/utils';

interface ISelectOption {
label: string;
value: string;
}

interface ISelectBoxProps {
defaultValue?: ISelectOption;
placeholder?: string;
onChange?: (item: ISelectOption) => void;
options: ISelectOption[];
addable?: boolean;
onAdd?: (term: string) => void;
reversed?: boolean;
addText?: string;
}

const SearchableSelectBox: React.FC<ISelectBoxProps> = (props) => {
const [isOpen, setIsOpen] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const searchRef = useRef<HTMLInputElement>(null);
const [isMouseIn, setIsMouseIn] = useState(false);

const optionChanged = (option: ISelectOption) => {
setIsOpen(false);
if(props.onChange)
props.onChange(option);
}

const addOptionClicked = (searchTerm: string) => {
setSearchTerm("");
setIsOpen(false);
if(props.onAdd)
props.onAdd(searchTerm);
}

useEffect(() => {
setSearchTerm("");
}, [isOpen]);

const selectBoxClicked = () => {
if(!isOpen) {
searchRef.current?.focus();
}
setIsOpen(!isOpen);
}

let searchResults = props.options.filter(option => option.label.toLowerCase().includes(searchTerm.trim()) || option.value.toLowerCase().includes(searchTerm.trim()));

return (
<div className={cx(styles.selectBoxContainer, isOpen ? styles.open : "", props.reversed ? styles.reversed : "")} onMouseEnter={(e) => setIsMouseIn(true)} onMouseLeave={(e) => setIsMouseIn(false)}>
<div className={styles.selectBox}>
<div className={styles.selectedOption} onClick={() => selectBoxClicked()}>{props.defaultValue?.label || (<span className={styles.placeholder}>{props.placeholder || "Select an item"}</span>)}</div>
<ul className={styles.options}>
<li className={styles.searchInputContainer}>
<input type="text" ref={searchRef} placeholder="Filter results" value={searchTerm} onBlur={(e) => setIsOpen(isMouseIn)} onChange={(e) => setSearchTerm(e.target.value)} />
</li>
{props.addable && searchTerm.trim().length ? (
<li className={styles.option} onClick={(e) => addOptionClicked(searchTerm)}>{props.addText ? props.addText.replaceAll("{term}", searchTerm) : `Add "${searchTerm}"`}</li>
) : !searchResults.length ? (
<li className={styles.noResults}>No results</li>
) : null}
{searchResults.map(option => (
<li key={option.label + option.value} className={styles.option} onClick={(e) => optionChanged(option)}>{option.label}</li>
))}
</ul>
</div>
</div>
);
};

export default SearchableSelectBox;
42 changes: 40 additions & 2 deletions src/popup/components/forms/ConnectionSettingsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { useDispatch } from 'react-redux';
import { setConfig } from '../../redux/config/configActions';
import { Dispatch } from 'redux';
import { browser } from '@/general/utils';
import SearchableSelectBox from '@/popup/common-components/inputs/SearchableSelectBox';
import { getContextBreadcrumbs, showSuccessMessage } from '@/popup/utils';

interface ConnectionSettingsFormTypes {
closePopup?: React.MouseEventHandler<HTMLDivElement>;
Expand All @@ -18,6 +20,33 @@ const ConnectionSettingsForm: React.FC<ConnectionSettingsFormTypes> = ({ closePo
setTransport({...config.transport});
}, [config.transport]);

const [sessions, setSessions] = useState(["/work/foo", "/work/bar"]);
const [selectedSession, setSelectedSession] = useState(sessions[1]);

const sessionChanged = (e) => {
setSelectedSession(e.value);
}

const addSession = (session: string) => {
showSuccessMessage(`Lets suppose session ${session} added`);
setSessions(sessions => {
sessions.push(session.toLowerCase());
return sessions;
})
}

const onAddSession = (session: string) => {
const s = sessions.find(s => s.toLowerCase().endsWith(`/${session.trim()}`));
if(s) {
// already added
setSelectedSession(s);
} else {
const addableSession = `/work/${session.replace("/work/", "")}`; // TODO make it better
addSession(addableSession);
setSelectedSession(addableSession)
}
}

const saveConnectionSettings = (e: any, config: IConfigProps) => {
if(closePopup) closePopup(e);
dispatch(setConfig(config));
Expand Down Expand Up @@ -68,9 +97,18 @@ const ConnectionSettingsForm: React.FC<ConnectionSettingsFormTypes> = ({ closePo
</div>

<div className="input-container">
<label className="form-label" htmlFor="connection-setting-pinToContext">Pin To Context</label>
<label className="form-label" htmlFor="connection-setting-pinToContext">Pin Session To Context</label>
<div className="form-control">
<input type="text" id="connection-setting-pinToContext" value={transport.pinToContext} onChange={(e) => setTransport({...transport, pinToContext: e.target.value })} />
<SearchableSelectBox
addable={true}
reversed={true}
options={sessions.map(session => ({ label: getContextBreadcrumbs(session).map(cb => cb.textContent).join(" > "), value: session }))}
onChange={sessionChanged}
onAdd={onAddSession}
addText={`Add /work/{term}`}
defaultValue={{ label: getContextBreadcrumbs(selectedSession).map(cb => cb.textContent).join(" > "), value: selectedSession }}
/>
{/* <Select options={sessions} defaultValue={defaultValue} onChange={sessionChanged} filterOption={filterOption}/> */}
</div>
</div>

Expand Down

0 comments on commit 5c47480

Please sign in to comment.