Skip to content

Commit

Permalink
Time machine updates (#9693)
Browse files Browse the repository at this point in the history
* Replace slider with tree view

* more redesign work

* add entry for now and set interval to 5 minutes
  • Loading branch information
riknoll authored Sep 22, 2023
1 parent e93c4ae commit 8b2f251
Show file tree
Hide file tree
Showing 11 changed files with 673 additions and 155 deletions.
48 changes: 42 additions & 6 deletions react-common/components/controls/FocusList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,42 @@ export const FocusList = (props: FocusListProps) => {
}
}

const isFocusable = (e: HTMLElement) => {
return e.getAttribute("data-isfocusable") === "true"
&& getComputedStyle(e).display !== "none";
}

const firstFocusableElement = () => {
return focusableElements.find(e => isFocusable(e))
}

const lastFocusableElement = () => {
for (let i = 0; i < focusableElements.length; i++) {
if (isFocusable(focusableElements[focusableElements.length - 1 - i])) {
return focusableElements[focusableElements.length - 1 - i];
}
}

return focusableElements[0];
}

const nextFocusableElement = (index: number, forwards: boolean) => {
let current: HTMLElement
for (let i = 1; i < focusableElements.length; i++) {
if (forwards) {
current = focusableElements[(index + i) % focusableElements.length];
}
else {
current = focusableElements[(index + focusableElements.length - i) % focusableElements.length];
}

if (isFocusable(current)) {
return current;
}
}
return focusableElements[0];
}

const onKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
if (!focusableElements?.length) return;

Expand Down Expand Up @@ -84,31 +120,31 @@ export const FocusList = (props: FocusListProps) => {
}
else if (e.key === (useUpAndDownArrowKeys ? "ArrowDown" : "ArrowRight")) {
if (index === focusableElements.length - 1 || target === focusList) {
focus(focusableElements[0]);
focus(firstFocusableElement());
}
else {
focus(focusableElements[index + 1]);
focus(nextFocusableElement(index, true));
}
e.preventDefault();
e.stopPropagation();
}
else if (e.key === (useUpAndDownArrowKeys ? "ArrowUp" : "ArrowLeft")) {
if (index === 0 || target === focusList) {
focus(focusableElements[focusableElements.length - 1]);
focus(lastFocusableElement());
}
else {
focus(focusableElements[Math.max(index - 1, 0)]);
focus(nextFocusableElement(index, false));
}
e.preventDefault();
e.stopPropagation();
}
else if (e.key === "Home") {
focus(focusableElements[0]);
focus(firstFocusableElement());
e.preventDefault();
e.stopPropagation();
}
else if (e.key === "End") {
focus(focusableElements[focusableElements.length - 1]);
focus(lastFocusableElement());
e.preventDefault();
e.stopPropagation();
}
Expand Down
153 changes: 153 additions & 0 deletions react-common/components/controls/Tree.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import * as React from "react";

import { ContainerProps, classList } from "../util";
import { FocusList } from "./FocusList";

export interface TreeProps extends ContainerProps {
role?: "tree" | "group";
}

export interface TreeItemProps extends ContainerProps {
role?: "treeitem";
onClick?: () => void;
initiallyExpanded?: boolean;
}

export interface TreeItemBodyProps extends ContainerProps {
}

export const Tree = (props: TreeProps) => {
const {
children,
id,
className,
ariaLabel,
ariaHidden,
ariaDescribedBy,
role,
} = props;

if (!role || role === "tree") {
return (
<FocusList
className={classList("common-tree", className)}
id={id}
aria-label={ariaLabel}
aria-hidden={ariaHidden}
aria-describedby={ariaDescribedBy}
role={role || "tree"}
>
{children}
</FocusList>
)
}

return (
<div
className={classList("common-tree", "subtree", className)}
id={id}
aria-label={ariaLabel}
aria-hidden={ariaHidden}
aria-describedby={ariaDescribedBy}
role={role || "tree"}
>
{children}
</div>
)
};

export const TreeItem = (props: TreeItemProps) => {
const {
children,
id,
className,
ariaLabel,
ariaHidden,
ariaDescribedBy,
role,
initiallyExpanded,
onClick
} = props;

const [expanded, setExpanded] = React.useState(initiallyExpanded);
const mappedChildren = React.Children.toArray(children);
const hasSubtree = mappedChildren.length > 1;

const subtreeContainer = React.useRef<HTMLDivElement>()

React.useEffect(() => {
if (!hasSubtree) return;

if (expanded) {
const focusable = subtreeContainer.current.querySelectorAll(`[tabindex]:not([tabindex="-1"]),[data-isfocusable]`);
focusable.forEach(f => f.setAttribute("data-isfocusable", "true"));
}
else {
const focusable = subtreeContainer.current.querySelectorAll(`[tabindex]:not([tabindex="-1"]),[data-isfocusable]`);
focusable.forEach(f => f.setAttribute("data-isfocusable", "false"));
}
}, [expanded, hasSubtree]);

const onTreeItemClick = React.useCallback(() => {
if (hasSubtree) {
setExpanded(!expanded);
}
if (onClick) {
onClick();
}
}, [hasSubtree, expanded])

return (
<div
className="common-treeitem-container"
id={id}
aria-label={ariaLabel}
aria-hidden={ariaHidden}
aria-describedby={ariaDescribedBy}
role={role || "treeitem"}

>
<div className={classList("common-treeitem", className)} onClick={onTreeItemClick}>
{hasSubtree &&
<i
className={classList("fas", expanded ? "fa-chevron-down" : "fa-chevron-right")}
aria-hidden={true}
/>
}
{mappedChildren[0]}
</div>
<div
ref={subtreeContainer}
style={{ display: expanded ? undefined : "none" }}
>
{hasSubtree ? mappedChildren[1] : undefined}
</div>
</div>
);
}

export const TreeItemBody = (props: TreeItemBodyProps) => {
const {
children,
id,
className,
ariaLabel,
ariaHidden,
ariaDescribedBy,
role,
} = props;

return (
<div
className={classList("common-treeitembody", className)}
id={id}
aria-label={ariaLabel}
aria-hidden={ariaHidden}
aria-describedby={ariaDescribedBy}
role={role}
tabIndex={0}
>
{children}
</div>
);
}
33 changes: 33 additions & 0 deletions react-common/styles/controls/Tree.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.common-tree {
display: flex;
flex-direction: column;
}

.common-tree.subtree .common-treeitem {
// The width of the chevron is 1.81rem + 0.25rem margin
padding-left: 2.06rem;
}

.common-treeitem {
display: flex;
flex-direction: row;
align-items: center;

height: 3rem;
cursor: pointer;

background-color: @treeitemBackgroundColor;

text-wrap: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}

.common-treeitem:hover {
filter: grayscale(.15) brightness(.85) contrast(1.3);
}

.common-treeitem-container {
display: flex;
flex-direction: column;
}
9 changes: 8 additions & 1 deletion react-common/styles/react-common-variables.less
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,11 @@

@progressBarBorder: #7D7D7D;
@progressBarUnfilledColor: #EEE;
@progressBarFilledColor: #5874e1;
@progressBarFilledColor: #5874e1;


/****************************************************
* Tree View *
****************************************************/

@treeitemBackgroundColor: #ffffff;
1 change: 1 addition & 0 deletions react-common/styles/react-common.less
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
@import "controls/RadioButtonGroup.less";
@import "controls/Spinner.less";
@import "controls/Textarea.less";
@import "controls/Tree.less";
@import "controls/VerticalResizeContainer.less";
@import "controls/VerticalSlider.less";
@import "./react-common-variables.less";
Expand Down
Loading

0 comments on commit 8b2f251

Please sign in to comment.