-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[dashboard] new workspace with options
- Loading branch information
1 parent
9ca833a
commit 03ba412
Showing
14 changed files
with
617 additions
and
169 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
/** | ||
* Copyright (c) 2022 Gitpod GmbH. All rights reserved. | ||
* Licensed under the GNU Affero General Public License (AGPL). | ||
* See License.AGPL.txt in the project root for license information. | ||
*/ | ||
|
||
import React, { FunctionComponent, useMemo, useState } from "react"; | ||
import Arrow from "./Arrow"; | ||
|
||
export interface DropDown2Element { | ||
id: string; | ||
element: JSX.Element; | ||
isSelectable?: boolean; | ||
} | ||
|
||
export interface DropDown2Props { | ||
getElements: (searchString: string) => DropDown2Element[]; | ||
searchPlaceholder?: string; | ||
disableSearch?: boolean; | ||
onSelectionChange: (id: string) => void; | ||
} | ||
|
||
export const DropDown2: FunctionComponent<DropDown2Props> = (props) => { | ||
const [showDropDown, setShowDropDown] = useState<boolean>(false); | ||
const onSelected = useMemo( | ||
() => (elementId: string) => { | ||
props.onSelectionChange(elementId); | ||
setShowDropDown(false); | ||
}, | ||
[props], | ||
); | ||
const [search, setSearch] = useState<string>(""); | ||
const filteredOptions = props.getElements(search); | ||
const [selectedElementTemp, setSelectedElementTemp] = useState<string | undefined>(filteredOptions[0]?.id); | ||
|
||
const onKeyDown = useMemo( | ||
() => (e: React.KeyboardEvent) => { | ||
if (!showDropDown) { | ||
return; | ||
} | ||
if (e.key === "ArrowDown") { | ||
e.preventDefault(); | ||
let idx = filteredOptions.findIndex((e) => e.id === selectedElementTemp); | ||
while (idx++ < filteredOptions.length - 1) { | ||
const candidate = filteredOptions[idx]; | ||
if (candidate.isSelectable) { | ||
setSelectedElementTemp(candidate.id); | ||
return; | ||
} | ||
} | ||
return; | ||
} | ||
if (e.key === "ArrowUp") { | ||
e.preventDefault(); | ||
let idx = filteredOptions.findIndex((e) => e.id === selectedElementTemp); | ||
while (idx-- > 0) { | ||
const candidate = filteredOptions[idx]; | ||
if (candidate.isSelectable) { | ||
setSelectedElementTemp(candidate.id); | ||
return; | ||
} | ||
} | ||
return; | ||
} | ||
if (e.key === "Escape") { | ||
setShowDropDown(false); | ||
e.preventDefault(); | ||
} | ||
if (e.key === "Enter" && selectedElementTemp && filteredOptions.some((e) => e.id === selectedElementTemp)) { | ||
e.preventDefault(); | ||
props.onSelectionChange(selectedElementTemp); | ||
setShowDropDown(false); | ||
} | ||
}, | ||
[filteredOptions, props, selectedElementTemp, showDropDown], | ||
); | ||
|
||
const onBlur = useMemo( | ||
() => (e: React.FocusEvent) => { | ||
setShowDropDown(false); | ||
}, | ||
[setShowDropDown], | ||
); | ||
|
||
const doShowDropDown = useMemo(() => () => setShowDropDown(true), []); | ||
return ( | ||
<div onKeyDown={onKeyDown} onBlur={onBlur} className="relative flex flex-col"> | ||
<div | ||
className={ | ||
"h-16 rounded-lg bg-gray-100 dark:bg-gray-800 flex items-center px-2 " + | ||
(!showDropDown && " hover:bg-gray-200 dark:hover:bg-gray-700 cursor-pointer") | ||
} | ||
onClick={doShowDropDown} | ||
> | ||
{props.children} | ||
<div className="flex-grow" /> | ||
<div className="mr-2"> | ||
<Arrow direction={showDropDown ? "up" : "down"} /> | ||
</div> | ||
</div> | ||
{showDropDown && ( | ||
<div className="absolute w-full top-10 bg-gray-100 dark:bg-gray-900 max-h-72 overflow-auto rounded-lg mt-3 z-50 p-2"> | ||
{!props.disableSearch ? ( | ||
<div className="h-12"> | ||
<input | ||
type="text" | ||
autoFocus | ||
className="w-full focus rounded-lg" | ||
placeholder={props.searchPlaceholder} | ||
value={search} | ||
onChange={(e) => setSearch(e.target.value)} | ||
/> | ||
</div> | ||
) : ( | ||
<div className="text-gray-500 pt-2 ml-2">{props.searchPlaceholder}</div> | ||
)} | ||
<ul> | ||
{filteredOptions.length > 0 ? ( | ||
filteredOptions.map((element) => { | ||
let selectionClasses = `dark:bg-gray-900 cursor-pointer`; | ||
if (element.id === selectedElementTemp) { | ||
selectionClasses = `bg-gray-200 dark:bg-gray-700 cursor-pointer`; | ||
} | ||
if (!element.isSelectable) { | ||
selectionClasses = ``; | ||
} | ||
return ( | ||
<li | ||
key={element.id} | ||
className={"h-16 rounded-lg flex items-center px-2 " + selectionClasses} | ||
onMouseDown={() => { | ||
if (element.isSelectable) { | ||
setSelectedElementTemp(element.id); | ||
onSelected(element.id); | ||
} | ||
}} | ||
onMouseOver={() => setSelectedElementTemp(element.id)} | ||
> | ||
{element.element} | ||
</li> | ||
); | ||
}) | ||
) : ( | ||
<li key="no-elements" className={"rounded-md "}> | ||
<div className="h-12 pl-8 py-3 text-gray-800 dark:text-gray-200">No results</div> | ||
</li> | ||
)} | ||
</ul> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.