Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenthoms committed Jul 17, 2024
1 parent 1e78bf3 commit cbab61a
Show file tree
Hide file tree
Showing 29 changed files with 779 additions and 979 deletions.
8 changes: 7 additions & 1 deletion frontend/src/lib/components/Dropdown/dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,12 @@ export function Dropdown<TValue = string>(props: DropdownProps<TValue>) {
React.useEffect(
function addKeyDownEventHandler() {
function handleKeyDown(e: KeyboardEvent) {
if (e.key === "Escape") {
setDropdownVisible(false);
setOptionIndexWithFocus(-1);
setKeyboardFocus(false);
inputRef.current?.blur();
}
if (dropdownRef.current) {
const currentStartIndex = Math.round(dropdownRef.current?.scrollTop / optionHeight);
if (dropdownVisible) {
Expand Down Expand Up @@ -369,7 +375,7 @@ export function Dropdown<TValue = string>(props: DropdownProps<TValue>) {

return (
<BaseComponent disabled={props.disabled}>
<div style={{ width: props.width }} id={props.wrapperId} className="flex">
<div style={{ width: props.width }} id={props.wrapperId} className="flex hover input-comp rounded">
<div className="flex-grow">
<Input
ref={inputRef}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/components/Select/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ export function Select<TValue = string>(props: SelectProps<TValue>) {
/>
)}
<div
className="overflow-y-auto border border-gray-300 rounded-md w-full bg-white"
className="overflow-y-auto border border-gray-300 rounded-md w-full bg-white input-comp"
style={{ height: sizeWithDefault * 24 + 2 }}
ref={ref}
tabIndex={0}
Expand Down
210 changes: 139 additions & 71 deletions frontend/src/lib/components/Table/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,33 +69,43 @@ function filterData(
});
}

function sortData(
type SortColumnAndDirectionElement = {
col: string;
dir: SortDirection;
};

function sortDataByColumns(
data: IdentifiedTableRow<TableHeading>[],
col: string,
dir: SortDirection
sortColumnAndDirectionArray: SortColumnAndDirectionElement[]
): IdentifiedTableRow<TableHeading>[] {
return [
...data.sort((a, b) => {
const aValue = a.values[col];
const bValue = b.values[col];
if (aValue === null && bValue === null) {
return 0;
}
if (aValue === null) {
return dir === SortDirection.ASC ? 1 : -1;
}
if (bValue === null) {
return dir === SortDirection.ASC ? -1 : 1;
}
if (aValue < bValue) {
return dir === SortDirection.ASC ? -1 : 1;
}
if (aValue > bValue) {
return dir === SortDirection.ASC ? 1 : -1;
}
return 0;
}),
];
return [...data.sort((a, b) => compareDataByColumns(a, b, sortColumnAndDirectionArray))];
}

function compareDataByColumns(
a: IdentifiedTableRow<TableHeading>,
b: IdentifiedTableRow<TableHeading>,
sortColumnAndDirectionArray: SortColumnAndDirectionElement[]
): number {
for (const { col, dir } of sortColumnAndDirectionArray) {
const aValue = a.values[col];
const bValue = b.values[col];
if (aValue === null && bValue === null) {
continue;
}
if (aValue === null) {
return dir === SortDirection.ASC ? 1 : -1;
}
if (bValue === null) {
return dir === SortDirection.ASC ? -1 : 1;
}
if (aValue < bValue) {
return dir === SortDirection.ASC ? -1 : 1;
}
if (aValue > bValue) {
return dir === SortDirection.ASC ? 1 : -1;
}
}
return 0;
}

function preprocessData(data: TableRow<TableHeading>[]): IdentifiedTableRow<TableHeading>[] {
Expand All @@ -112,23 +122,25 @@ export const Table: React.FC<TableProps<TableHeading>> = (props) => {
const [preprocessedData, setPreprocessedData] = React.useState<IdentifiedTableRow<TableHeading>[]>([]);
const [filteredData, setFilteredData] = React.useState<IdentifiedTableRow<TableHeading>[]>([]);
const [filterValues, setFilterValues] = React.useState<{ [key: string]: string }>({});
const [sortColumnAndDirection, setSortColumnAndDirection] = React.useState<{ col: string; dir: SortDirection }>({
col: "",
dir: SortDirection.ASC,
});
const containerRef = React.useRef<HTMLDivElement>(null);
const [sortColumnAndDirectionArray, setSortColumnAndDirectionArray] = React.useState<
SortColumnAndDirectionElement[]
>([]);

React.useEffect(() => {
setPreprocessedData(preprocessData(props.data));
}, [props.data]);
const [prevData, setPrevData] = React.useState<TableRow<TableHeading>[]>([]);

React.useEffect(() => {
setFilteredData(filterData(preprocessedData, filterValues, props.headings));
}, [preprocessedData, filterValues, props.headings]);
const containerRef = React.useRef<HTMLDivElement>(null);

React.useEffect(() => {
setFilteredData((prev) => sortData(prev, sortColumnAndDirection.col, sortColumnAndDirection.dir));
}, [sortColumnAndDirection]);
if (prevData !== props.data) {
setPrevData(props.data);
const newPreprocessedData = preprocessData(props.data);
setPreprocessedData(newPreprocessedData);
setFilteredData(
sortDataByColumns(
filterData(newPreprocessedData, filterValues, props.headings),
sortColumnAndDirectionArray
)
);
}

React.useEffect(() => {
const maxNumberOfSubheadings = Object.keys(props.headings).length;
Expand Down Expand Up @@ -157,16 +169,99 @@ export const Table: React.FC<TableProps<TableHeading>> = (props) => {

function handleFilterChange(col: string, value: string) {
setFilterValues({ ...filterValues, [col]: value });
setFilteredData(
sortDataByColumns(filterData(preprocessedData, filterValues, props.headings), sortColumnAndDirectionArray)
);
}

function handleSortDirectionChange(col: string, dir: SortDirection) {
setSortColumnAndDirection({ col, dir });
function handleSortDirectionChange(event: React.MouseEvent<HTMLDivElement>, col: string, dir: SortDirection) {
const sortColumnAndDirectionElement: SortColumnAndDirectionElement = {
col,
dir,
};

let newSortColumnAndDirectionArray: SortColumnAndDirectionElement[] = [];

if (event.shiftKey) {
const element = sortColumnAndDirectionArray.find((el) => el.col === col);
if (element && element.dir === dir) {
newSortColumnAndDirectionArray = sortColumnAndDirectionArray.filter((el) => el.col !== col);
} else if (element) {
newSortColumnAndDirectionArray = sortColumnAndDirectionArray.filter((el) => el.col !== col);
newSortColumnAndDirectionArray = [...newSortColumnAndDirectionArray, sortColumnAndDirectionElement];
} else {
newSortColumnAndDirectionArray = [...sortColumnAndDirectionArray, sortColumnAndDirectionElement];
}
} else {
newSortColumnAndDirectionArray = [sortColumnAndDirectionElement];
}

setSortColumnAndDirectionArray(newSortColumnAndDirectionArray);
sortDataByColumns(filteredData, newSortColumnAndDirectionArray);
}

if (layoutError.error) {
return <div>{layoutError.message}</div>;
}

function makeSortButtons(col: string): React.ReactNode {
let sortDirection: SortDirection | null = null;
let numSortColumn = 0;
if (sortColumnAndDirectionArray.length > 0) {
const index = sortColumnAndDirectionArray.findIndex((el) => el.col === col);
if (index !== -1) {
numSortColumn = index + 1;
sortDirection = sortColumnAndDirectionArray[index].dir;
}
}

const component = (
<div className="flex flex-col h-8">
<div
className={resolveClassNames(
"text-sm hover:text-blue-500 cursor-pointer h-4",
sortDirection === SortDirection.ASC
? "text-white bg-blue-800 hover:text-blue-100"
: "text-blue-300 hover:text-white hover:bg-blue-300"
)}
onClick={(e) => handleSortDirectionChange(e, col, SortDirection.ASC)}
title="Sort ascending"
>
<div className="-mt-0.5">
<ExpandLess fontSize="inherit" />
</div>
</div>
<div
className={resolveClassNames(
"text-sm hover:text-blue-500 cursor-pointer h-4",
sortDirection === SortDirection.DESC
? "text-white bg-blue-800 hover:text-blue-100"
: "text-blue-300 hover:text-white hover:bg-blue-300"
)}
onClick={(e) => handleSortDirectionChange(e, col, SortDirection.DESC)}
title="Sort descending"
>
<div className="-mt-1">
<ExpandMore fontSize="inherit" />
</div>
</div>
</div>
);

if (sortColumnAndDirectionArray.length <= 1 || numSortColumn === 0) {
return component;
}

return (
<div className="flex gap-1 items-center">
<div className="rounded-full bg-blue-800 text-white h-4 w-4 flex items-center justify-center text-xs pt-0.5">
{numSortColumn}
</div>
{component}
</div>
);
}

return (
<BaseComponent disabled={props.disabled}>
<div
Expand All @@ -186,34 +281,7 @@ export const Table: React.FC<TableProps<TableHeading>> = (props) => {
>
<div className="px-1 flex items-center">
<span className="flex-grow">{props.headings[col].label}</span>
<div className="flex flex-col">
<div
className={resolveClassNames(
"text-xs hover:text-blue-500 cursor-pointer h-4",
sortColumnAndDirection.col === col &&
sortColumnAndDirection.dir === SortDirection.ASC
? "text-blue-600"
: "text-blue-300"
)}
onClick={() => handleSortDirectionChange(col, SortDirection.ASC)}
title="Sort ascending"
>
<ExpandLess fontSize="inherit" />
</div>
<div
className={resolveClassNames(
"text-xs hover:text-blue-500 cursor-pointer h-4",
sortColumnAndDirection.col === col &&
sortColumnAndDirection.dir === SortDirection.DESC
? "text-blue-600"
: "text-blue-300"
)}
onClick={() => handleSortDirectionChange(col, SortDirection.DESC)}
title="Sort descending"
>
<ExpandMore fontSize="inherit" />
</div>
</div>
{makeSortButtons(col)}
</div>
<div className="p-0 text-sm">
<Input
Expand Down Expand Up @@ -252,9 +320,9 @@ export const Table: React.FC<TableProps<TableHeading>> = (props) => {
key={item.id}
className={`${
props.highlightFilter && props.highlightFilter(item.values)
? "bg-blue-50 "
? "bg-blue-100 "
: ""
} hover:bg-blue-100`}
} hover:bg-blue-50`}
onPointerOver={() => handlePointerOver(item.values)}
onPointerDown={() => handlePointerDown(item.values)}
style={{ height: 30 }}
Expand Down
36 changes: 23 additions & 13 deletions frontend/src/lib/components/TagPicker/tagPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type DropdownRect = {
const NO_MATCHING_TAGS_TEXT = "No matching tags";
const NO_TAGS_TEXT = "No tags";

export function TagPicker<T extends string>(props: TagPickerProps<T>): React.ReactElement {
export function TagPicker<T>(props: TagPickerProps<T>): React.ReactElement {
const [selectedTags, setSelectedTags] = React.useState<T[]>(props.value);
const [prevSelectedTags, setPrevSelectedTags] = React.useState<T[]>(props.value);
const [dropdownVisible, setDropdownVisible] = React.useState<boolean>(false);
Expand Down Expand Up @@ -95,7 +95,17 @@ export function TagPicker<T extends string>(props: TagPickerProps<T>): React.Rea
}
}

function handleKeyDown(event: KeyboardEvent) {
if (event.key === "Escape") {
setDropdownVisible(false);
inputRef.current?.blur();
setFocused(false);
return;
}
}

document.addEventListener("mousedown", handleMouseDown);
document.addEventListener("keydown", handleKeyDown);

return () => {
document.removeEventListener("mousedown", handleMouseDown);
Expand Down Expand Up @@ -189,6 +199,8 @@ export function TagPicker<T extends string>(props: TagPickerProps<T>): React.Rea
newSelectedTags.push(value);
}

setFilter(null);
inputRef.current?.focus();
setSelectedTags(newSelectedTags);

if (props.debounceTimeMs) {
Expand Down Expand Up @@ -243,12 +255,9 @@ export function TagPicker<T extends string>(props: TagPickerProps<T>): React.Rea
style={{ width: props.width }}
id={props.wrapperId}
ref={divRef}
className={resolveClassNames(
"flex w-full border p-1 px-2 rounded text-sm shadow-sm hover:border-blue-300",
{
"outline outline-blue-500": focused,
}
)}
className={resolveClassNames("flex w-full border p-1 px-2 rounded text-sm shadow-sm input-comp", {
"outline outline-blue-500": focused,
})}
onClick={handleClick}
>
<div className="min-h-6 flex-grow flex gap-2 justify-start flex-wrap">
Expand All @@ -257,22 +266,23 @@ export function TagPicker<T extends string>(props: TagPickerProps<T>): React.Rea
if (!tagOption) {
return null;
}
return <Tag key={tag.toString()} tag={tagOption} onRemove={() => removeTag(tag)} />;
return <Tag key={`${tag}`} tag={tagOption} onRemove={() => removeTag(tag)} />;
})}
<input
ref={inputRef}
className="flex-grow outline-none min-w-0 h-8"
className="flex-grow outline-none min-w-0 h-8 w-0"
onClick={handleInputClick}
onChange={handleInputChange}
onFocus={handleFocus}
onBlur={handleBlur}
value={filter ?? ""}
/>
</div>
<div className="h-8 flex flex-col justify-center">
<div className="h-8 flex flex-col justify-center cursor-pointer">
{selectedTags.length === 0 ? (
<ExpandMore fontSize="inherit" />
) : (
<IconButton onClick={handleClearAll}>
<IconButton onClick={handleClearAll} title="Clear selection">
<Close fontSize="inherit" />
</IconButton>
)}
Expand Down Expand Up @@ -318,10 +328,10 @@ type TagProps<T> = {

function Tag<T>(props: TagProps<T>): React.ReactNode {
return (
<div className="bg-blue-200 p-1 rounded flex gap-1 items-center">
<div className="bg-blue-200 p-1 pl-2 rounded flex gap-1 items-center input-comp">
<span>{props.tag.label}</span>
{
<IconButton onClick={props.onRemove}>
<IconButton onClick={props.onRemove} title="Remove tag">
<Close fontSize="inherit" />
</IconButton>
}
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
@tailwind components;
@tailwind utilities;

/* Additional tailwind component classes */
@layer components {
.input-comp {
@apply hover:outline hover:outline-1 hover:outline-blue-300 focus:outline focus:outline-1 focus:outline-blue-600;
}
}

/* Custom CSS styles*/

body {
overflow: hidden;
font-family: Equinor;
Expand Down
Loading

0 comments on commit cbab61a

Please sign in to comment.