Files
eveship.fit.react/src/TreeListing/TreeListing.tsx

128 lines
3.4 KiB
TypeScript

import clsx from "clsx";
import React from "react";
import { Icon, IconName } from "../Icon";
import styles from "./TreeListing.module.css";
interface Tree {
size: number;
}
export const TreeContext = React.createContext<Tree>({ size: 24 });
/**
* Action (the icon on the right side of the header) for a header.
*/
export const TreeHeaderAction = (props: {
icon: IconName;
onClick: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
}) => {
const tree = React.useContext(TreeContext);
return (
<div className={styles.headerAction} onClick={props.onClick}>
<Icon name={props.icon} size={tree.size} />
</div>
);
};
/**
* Header for a listing.
*/
export const TreeHeader = (props: { icon?: string; text: string; action?: React.ReactNode }) => {
const tree = React.useContext(TreeContext);
return (
<>
{props.icon !== undefined && (
<span>
<img src={props.icon} height={tree.size} width={tree.size} alt="" />
</span>
)}
<span className={styles.headerText}>{props.text}</span>
{props.action && <span>{props.action}</span>}
</>
);
};
export const TreeLeaf = (props: {
level: number;
height?: number;
icon?: IconName;
iconTitle?: string;
content: string;
onClick?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
}) => {
const stylesHeader = styles[`header${props.level}`];
const height = props.height ?? 20;
const style = { "--height": `${height}px` } as React.CSSProperties;
return (
<div>
<TreeContext.Provider value={{ size: height }}>
<div
style={style}
className={clsx(styles.header, stylesHeader, {
[styles.headerHover]: props.onClick !== undefined,
[styles.leaf]: props.onClick !== undefined,
})}
onClick={props.onClick}
>
{props.icon !== undefined && (
<span className={styles.leafIcon}>
<Icon name={props.icon} size={12} title={props.iconTitle} />
</span>
)}
<span className={styles.headerText}>{props.content}</span>
</div>
</TreeContext.Provider>
</div>
);
};
/**
* Tree listing for hulls, modules, and charges.
*/
export const TreeListing = (props: {
level: number;
header?: React.ReactNode;
height?: number;
getChildren: () => React.ReactNode;
}) => {
const [expanded, setExpanded] = React.useState(props.header === undefined);
const stylesHeader = styles[`header${props.level}`];
const stylesContent = styles[`content${props.level}`];
const height = props.height ?? 20;
const style = { "--height": `${height}px` } as React.CSSProperties;
let children: React.ReactNode = <></>;
/* Speed up rendering by not rendering children if we are collapsed. */
if (expanded) {
children = props.getChildren();
}
return (
<div>
<TreeContext.Provider value={{ size: height }}>
{props.header !== undefined && (
<div
style={style}
className={clsx(styles.header, styles.headerHover, stylesHeader)}
onClick={() => setExpanded((current) => !current)}
>
<span>
<Icon name={expanded ? "menu-expand" : "menu-collapse"} size={12} />
</span>
{props.header}
</div>
)}
<div className={clsx(stylesContent, { [styles.content]: props.header !== undefined })}>{children}</div>
</TreeContext.Provider>
</div>
);
};