1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Introduce new TreeView for use in CatalogMenu to fix tests

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2023-02-23 15:34:39 -05:00
parent 8025471eee
commit 0f72c118f2
8 changed files with 140 additions and 85 deletions

View File

@ -16,14 +16,15 @@ import { withInjectables } from "@ogre-tools/injectable-react";
import filteredCategoriesInjectable from "../../../common/catalog/filtered-categories.injectable"; import filteredCategoriesInjectable from "../../../common/catalog/filtered-categories.injectable";
import { TreeGroup, TreeItem, TreeView } from "../tree-view/tree-view"; import { TreeGroup, TreeItem, TreeView } from "../tree-view/tree-view";
import { browseCatalogTab } from "./catalog-browse-tab"; import { browseCatalogTab } from "./catalog-browse-tab";
import { HorizontalLine } from "../horizontal-line/horizontal-line";
export interface CatalogMenuProps { export interface CatalogMenuProps {
activeTab: string | undefined; activeTab: string | undefined;
onItemClick: (id: string) => void; onItemClick: (id: string) => void;
} }
function getCategoryIcon(category: CatalogCategory) { function CategoryIcon(props: { category: CatalogCategory }) {
const { icon } = category.metadata ?? {}; const { icon } = props.category.metadata ?? {};
if (typeof icon === "string") { if (typeof icon === "string") {
return Icon.isSvg(icon) return Icon.isSvg(icon)
@ -42,41 +43,38 @@ const NonInjectedCatalogMenu = observer(({
activeTab, activeTab,
filteredCategories, filteredCategories,
onItemClick, onItemClick,
}: CatalogMenuProps & Dependencies) => { }: CatalogMenuProps & Dependencies) => (
console.log(treeStyles); <div className="flex flex-col w-full">
<div className={styles.catalog}>Catalog</div>
return ( <TreeView>
<div className="flex flex-col w-full"> <TreeItem
<div className={styles.catalog}>Catalog</div> classes={treeStyles}
<TreeView> label="Browse"
<TreeItem data-testid="*-tab"
label="Browse" onClick={() => onItemClick("*")}
data-testid="*-tab" selected={activeTab === browseCatalogTab}
onClick={() => onItemClick("*")} />
selected={activeTab === browseCatalogTab} /> <HorizontalLine size="xxs" />
<TreeGroup <TreeGroup
classes={treeStyles} classes={treeStyles}
label={<div className={styles.parent}>Categories</div>} label={<div className={styles.parent}>Categories</div>}
> >
{filteredCategories.get() {filteredCategories.get()
.map(category => ( .map(category => (
<TreeItem <TreeItem
key={category.getId()} classes={treeStyles}
label={( key={category.getId()}
<> icon={<CategoryIcon category={category} />}
{getCategoryIcon(category)} label={<CatalogCategoryLabel category={category} />}
<CatalogCategoryLabel category={category} /> selected={activeTab === category.getId()}
</> data-testid={`${category.getId()}-tab`}
)} onClick={() => onItemClick(category.getId())}
selected={activeTab === category.getId()} />
data-testid={`${category.getId()}-tab`} ))}
onClick={() => onItemClick(category.getId())} /> </TreeGroup>
))} </TreeView>
</TreeGroup> </div>
</TreeView> ));
</div>
);
});
export const CatalogMenu = withInjectables<Dependencies, CatalogMenuProps>(NonInjectedCatalogMenu, { export const CatalogMenu = withInjectables<Dependencies, CatalogMenuProps>(NonInjectedCatalogMenu, {
getProps: (di, props) => ({ getProps: (di, props) => ({

View File

@ -23,39 +23,8 @@
.content { .content {
min-height: 26px; min-height: 26px;
line-height: 1.3; line-height: 1.3;
padding: 2px var(--padding) 2px 0;
&:hover {
background-color: var(--sidebarItemHoverBackground);
border-radius: 2px;
}
&:active {
color: white;
background-color: var(--blue);
}
} }
.group { .group {
margin-left: 0px; margin-left: 0px;
.iconContainer {
margin-left: 28px;
margin-top: 2px;
align-self: flex-start;
}
}
.selected {
& > *:first-child {
background-color: var(--blue);
color: white;
border-radius: 2px;
}
}
.iconContainer {
width: 21px;
margin-left: 5px;
margin-right: 0;
} }

View File

@ -177,7 +177,6 @@ class NonInjectedCatalog extends React.Component<Dependencies> {
} }
onTabChange = action((tabId: string | null) => { onTabChange = action((tabId: string | null) => {
console.log(tabId);
const activeCategory = this.categories.find(category => category.getId() === tabId); const activeCategory = this.categories.find(category => category.getId() === tabId);
this.props.emitEvent({ this.props.emitEvent({

View File

@ -20,6 +20,9 @@
$baseline: 8px; $baseline: 8px;
@include horizontalLineSize('xxs', 0.5 * $baseline);
@include horizontalLineSize('xs', 1 * $baseline);
@include horizontalLineSize('sm', 2 * $baseline); @include horizontalLineSize('sm', 2 * $baseline);
@include horizontalLineSize('md', 3 * $baseline); @include horizontalLineSize('md', 3 * $baseline);
@include horizontalLineSize('lg', 4 * $baseline);
@include horizontalLineSize('xl', 5 * $baseline); @include horizontalLineSize('xl', 5 * $baseline);

View File

@ -7,7 +7,7 @@ import styles from "./horizontal-line.module.scss";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
interface HorizontalLineProps { interface HorizontalLineProps {
size?: "sm" | "md" | "xl"; size?: "xxs" | "xs" | "sm" | "md" | "lg" | "xl";
} }
export const HorizontalLine = ({ size = "xl" }: HorizontalLineProps = { size: "xl" }) => { export const HorizontalLine = ({ size = "xl" }: HorizontalLineProps = { size: "xl" }) => {

View File

@ -0,0 +1,63 @@
.treeItem {
display: flex;
flex-direction: row;
padding: 2px var(--padding) 2px 0;
cursor: pointer;
&:hover {
background-color: var(--sidebarItemHoverBackground);
}
&.selected:hover {
background-color: var(--blue);
}
}
.selected {
background-color: var(--blue);
color: white;
border-radius: 2px;
width: 100%;
}
.treeGroup {
display: flex;
flex-direction: column;
cursor: pointer;
}
.contents {
padding-left: 25px;
transition: all 300ms ease;
overflow: hidden;
&.expanded {
max-height: 100%;
}
&:not(.expanded) {
max-height: 0;
}
}
.selected {
color: white;
background-color: var(--blue);
}
.group {
display: flex;
flex-direction: row;
}
.iconContainer {
align-self: flex-start;
width: 21px;
margin-left: 5px;
margin-right: 5px;
}
.treeView {
display: flex;
flex-direction: column;
}

View File

@ -3,6 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import styles from "./tree-view.module.scss";
import type { MouseEventHandler } from "react"; import type { MouseEventHandler } from "react";
import React, { useState } from "react"; import React, { useState } from "react";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
@ -20,7 +21,7 @@ export interface TreeViewProps {
export function TreeView(props: TreeViewProps) { export function TreeView(props: TreeViewProps) {
return ( return (
<ul <ul
className={props.classes?.root} className={cssNames(props.classes?.root, styles.treeView)}
role="tree" role="tree"
> >
{props.children} {props.children}
@ -31,10 +32,14 @@ export function TreeView(props: TreeViewProps) {
export interface TreeItemClasses { export interface TreeItemClasses {
root?: string; root?: string;
label?: string; label?: string;
selected?: string;
hover?: string;
iconContainer?: string;
} }
export interface TreeItemProps { export interface TreeItemProps {
classes?: TreeItemClasses; classes?: TreeItemClasses;
icon?: JSX.Element;
label: JSX.Element | string; label: JSX.Element | string;
testId?: string; testId?: string;
selected?: boolean; selected?: boolean;
@ -42,15 +47,31 @@ export interface TreeItemProps {
} }
export function TreeItem(props: TreeItemProps) { export function TreeItem(props: TreeItemProps) {
const [hovering, setHovering] = useState(false);
const optionalCssNames: Partial<Record<string, any>> = {};
if (props.classes?.selected) {
optionalCssNames[props.classes.selected] = props.selected ?? false;
}
if (props.classes?.hover) {
optionalCssNames[props.classes.hover] = hovering;
}
return ( return (
<li <li
className={cssNames(props.classes?.root, { className={cssNames(props.classes?.root, optionalCssNames, styles.treeItem, {
selected: props.selected ?? false, [styles.selected]: props.selected ?? false,
})} })}
role="treeitem" role="treeitem"
data-testid={props.testId} data-testid={props.testId}
onClick={props.onClick} onClick={props.onClick}
onMouseOver={() => setHovering(true)}
onMouseLeave={() => setHovering(false)}
> >
<div className={cssNames(props.classes?.iconContainer, styles.iconContainer)}>
{props.icon}
</div>
<div className={props.classes?.label}> <div className={props.classes?.label}>
{props.label} {props.label}
</div> </div>
@ -60,7 +81,7 @@ export function TreeItem(props: TreeItemProps) {
export interface TreeGroupClasses { export interface TreeGroupClasses {
root?: string; root?: string;
header?: string; group?: string;
iconContainer?: string; iconContainer?: string;
label?: string; label?: string;
contents?: string; contents?: string;
@ -81,12 +102,15 @@ export function TreeGroup(props: TreeGroupProps) {
return ( return (
<li <li
className={props.classes?.root} className={cssNames(props.classes?.root, styles.treeGroup)}
role="group" role="group"
data-testid={props.testId} data-testid={props.testId}
> >
<div className={props.classes?.header} onClick={() => setExpanded(!expanded)}> <div
<div className={props.classes?.iconContainer}> className={cssNames(props.classes?.group, styles.group)}
onClick={() => setExpanded(!expanded)}
>
<div className={cssNames(props.classes?.iconContainer, styles.iconContainer)}>
{ {
expanded expanded
? props.collapseIcon ?? <Icon material="expand_more" /> ? props.collapseIcon ?? <Icon material="expand_more" />
@ -97,13 +121,13 @@ export function TreeGroup(props: TreeGroupProps) {
{props.label} {props.label}
</div> </div>
</div> </div>
<div className={props.classes?.contents}> <ul
{ className={cssNames(props.classes?.contents, styles.contents, {
expanded [styles.expanded]: expanded,
? props.children })}
: null >
} {props.children}
</div> </ul>
</li> </li>
); );
} }

View File

@ -21,7 +21,6 @@
"skipLibCheck": true, "skipLibCheck": true,
"allowJs": false, "allowJs": false,
"esModuleInterop": true, "esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"importsNotUsedAsValues": "error", "importsNotUsedAsValues": "error",
"traceResolution": false, "traceResolution": false,
"resolveJsonModule": true, "resolveJsonModule": true,