Skip to content

Commit 03ba412

Browse files
committed
[dashboard] new workspace with options
1 parent 9ca833a commit 03ba412

14 files changed

+617
-169
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/**
2+
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import React, { FunctionComponent, useMemo, useState } from "react";
8+
import Arrow from "./Arrow";
9+
10+
export interface DropDown2Element {
11+
id: string;
12+
element: JSX.Element;
13+
isSelectable?: boolean;
14+
}
15+
16+
export interface DropDown2Props {
17+
getElements: (searchString: string) => DropDown2Element[];
18+
searchPlaceholder?: string;
19+
disableSearch?: boolean;
20+
onSelectionChange: (id: string) => void;
21+
}
22+
23+
export const DropDown2: FunctionComponent<DropDown2Props> = (props) => {
24+
const [showDropDown, setShowDropDown] = useState<boolean>(false);
25+
const onSelected = useMemo(
26+
() => (elementId: string) => {
27+
props.onSelectionChange(elementId);
28+
setShowDropDown(false);
29+
},
30+
[props],
31+
);
32+
const [search, setSearch] = useState<string>("");
33+
const filteredOptions = props.getElements(search);
34+
const [selectedElementTemp, setSelectedElementTemp] = useState<string | undefined>(filteredOptions[0]?.id);
35+
36+
const onKeyDown = useMemo(
37+
() => (e: React.KeyboardEvent) => {
38+
if (!showDropDown) {
39+
return;
40+
}
41+
if (e.key === "ArrowDown") {
42+
e.preventDefault();
43+
let idx = filteredOptions.findIndex((e) => e.id === selectedElementTemp);
44+
while (idx++ < filteredOptions.length - 1) {
45+
const candidate = filteredOptions[idx];
46+
if (candidate.isSelectable) {
47+
setSelectedElementTemp(candidate.id);
48+
return;
49+
}
50+
}
51+
return;
52+
}
53+
if (e.key === "ArrowUp") {
54+
e.preventDefault();
55+
let idx = filteredOptions.findIndex((e) => e.id === selectedElementTemp);
56+
while (idx-- > 0) {
57+
const candidate = filteredOptions[idx];
58+
if (candidate.isSelectable) {
59+
setSelectedElementTemp(candidate.id);
60+
return;
61+
}
62+
}
63+
return;
64+
}
65+
if (e.key === "Escape") {
66+
setShowDropDown(false);
67+
e.preventDefault();
68+
}
69+
if (e.key === "Enter" && selectedElementTemp && filteredOptions.some((e) => e.id === selectedElementTemp)) {
70+
e.preventDefault();
71+
props.onSelectionChange(selectedElementTemp);
72+
setShowDropDown(false);
73+
}
74+
},
75+
[filteredOptions, props, selectedElementTemp, showDropDown],
76+
);
77+
78+
const onBlur = useMemo(
79+
() => (e: React.FocusEvent) => {
80+
setShowDropDown(false);
81+
},
82+
[setShowDropDown],
83+
);
84+
85+
const doShowDropDown = useMemo(() => () => setShowDropDown(true), []);
86+
return (
87+
<div onKeyDown={onKeyDown} onBlur={onBlur} className="relative flex flex-col">
88+
<div
89+
className={
90+
"h-16 rounded-lg bg-gray-100 dark:bg-gray-800 flex items-center px-2 " +
91+
(!showDropDown && " hover:bg-gray-200 dark:hover:bg-gray-700 cursor-pointer")
92+
}
93+
onClick={doShowDropDown}
94+
>
95+
{props.children}
96+
<div className="flex-grow" />
97+
<div className="mr-2">
98+
<Arrow direction={showDropDown ? "up" : "down"} />
99+
</div>
100+
</div>
101+
{showDropDown && (
102+
<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">
103+
{!props.disableSearch ? (
104+
<div className="h-12">
105+
<input
106+
type="text"
107+
autoFocus
108+
className="w-full focus rounded-lg"
109+
placeholder={props.searchPlaceholder}
110+
value={search}
111+
onChange={(e) => setSearch(e.target.value)}
112+
/>
113+
</div>
114+
) : (
115+
<div className="text-gray-500 pt-2 ml-2">{props.searchPlaceholder}</div>
116+
)}
117+
<ul>
118+
{filteredOptions.length > 0 ? (
119+
filteredOptions.map((element) => {
120+
let selectionClasses = `dark:bg-gray-900 cursor-pointer`;
121+
if (element.id === selectedElementTemp) {
122+
selectionClasses = `bg-gray-200 dark:bg-gray-700 cursor-pointer`;
123+
}
124+
if (!element.isSelectable) {
125+
selectionClasses = ``;
126+
}
127+
return (
128+
<li
129+
key={element.id}
130+
className={"h-16 rounded-lg flex items-center px-2 " + selectionClasses}
131+
onMouseDown={() => {
132+
if (element.isSelectable) {
133+
setSelectedElementTemp(element.id);
134+
onSelected(element.id);
135+
}
136+
}}
137+
onMouseOver={() => setSelectedElementTemp(element.id)}
138+
>
139+
{element.element}
140+
</li>
141+
);
142+
})
143+
) : (
144+
<li key="no-elements" className={"rounded-md "}>
145+
<div className="h-12 pl-8 py-3 text-gray-800 dark:text-gray-200">No results</div>
146+
</li>
147+
)}
148+
</ul>
149+
</div>
150+
)}
151+
</div>
152+
);
153+
};

components/dashboard/src/components/Modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ type ModalBodyProps = {
123123
export const ModalBody = ({ children, hideDivider = false }: ModalBodyProps) => {
124124
return (
125125
<div
126-
className={cn("overflow-y-auto border-gray-200 dark:border-gray-800 -mx-6 px-6 ", {
126+
className={cn("border-gray-200 dark:border-gray-800 -mx-6 px-6 ", {
127127
"border-t border-b mt-2 py-4": !hideDivider,
128128
})}
129129
>

0 commit comments

Comments
 (0)