Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tree Item doesn't expand full leading to selection "breaking off" when user scrolls horizontally #291

Open
lkp-k opened this issue Jan 17, 2025 · 0 comments

Comments

@lkp-k
Copy link

lkp-k commented Jan 17, 2025

When there are long Tree Items, I want to be able to show them in a "selected" state where they have a background color applied to them. However, the background color does not extend fully.

Maybe pictures would help to explain the issue best -

Nothing selected - Note the first item is long and extends
Image

Scrolled to the end horizontally, and selected the first item
Image

Now, I've selected a child, and scrolled midway. You can see how the selection background color is cut off
Image

Is there a way to force the items to expand to the full width?

Here's my code:

import { Box, Typography } from "@mui/material";
import { blue, grey } from "@mui/material/colors";
import {
  FolderIcon,
  FolderOpenIcon,
  PackageIcon,
  TagIcon,
  UsersIcon,
} from "lucide-react";
import { useMemo } from "react";
import { Tree, NodeRendererProps, NodeApi } from "react-arborist";
import useResizeObserver from "use-resize-observer";
import { ResourceCategoryDTO, ResourceDTO } from "@shared-types";

import useGetResources from "../../../../../hooks/api/useGetResourcesQuery";

interface TreeNode {
  id: string;
  name: string;
  children?: TreeNode[];
  type: "category" | "resource";
  dto: ResourceCategoryDTO | ResourceDTO;
}

const buildTree = (
  categories: ResourceCategoryDTO[],
  resources: ResourceDTO[]
): TreeNode[] => {
  const categoryMap: { [key: string]: TreeNode } = {};

  categories.forEach((category) => {
    categoryMap[category.id] = {
      id: category.id,
      name: category.name,
      type: "category",
      children: [],
      dto: category,
    };
  });

  resources.forEach((resource) => {
    if (categoryMap[resource.categoryId]) {
      categoryMap[resource.categoryId].children?.push({
        id: resource.id,
        name: resource.name,
        type: "resource",
        dto: resource,
      });
    }
  });

  const tree: TreeNode[] = [];
  Object.values(categoryMap).forEach((category) => {
    if (!categories.find((cat) => cat.id === category.id)?.parentId) {
      tree.push(category);
    } else {
      const parentId = categories.find(
        (cat) => cat.id === category.id
      )?.parentId;
      if (parentId && categoryMap[parentId]) {
        categoryMap[parentId].children?.push(category);
      }
    }
  });

  return tree;
};

const ResourceTree = ({ organizationId }: { organizationId: string }) => {
  const { data: rcrData } = useGetResources(organizationId);

  const treeData = useMemo(
    () => buildTree(rcrData?.categories || [], rcrData?.resources || []),
    [rcrData]
  );

  const renderNodeLabel = (node: NodeApi<TreeNode>) => {
    return (
      <Box
        sx={{
          display: "flex",
          alignItems: "center",
          gap: 1.5,
          flexShrink: 0,
        }}
      >
        {node.data.type === "category" ? (
          (node.children?.length || 0) > 0 && node.isOpen ? (
            <FolderOpenIcon size={20} />
          ) : (
            <FolderIcon size={20} />
          )
        ) : node.data.type === "resource" ? (
          (node.data.dto as ResourceDTO).type === "labor" ? (
            <UsersIcon size={20} />
          ) : (node.data.dto as ResourceDTO).type === "non-labor" ? (
            <TagIcon size={20} />
          ) : (
            <PackageIcon size={20} />
          )
        ) : null}
        <Typography
          sx={{
            fontWeight: node.isFocused ? 500 : "",
            color: node.isFocused ? "text.primary" : grey[800],
            whiteSpace: "nowrap",
            textOverflow: "ellipsis",
            overflow: "hidden",
          }}
        >
          {node.data.name}
        </Typography>
      </Box>
    );
  };

  const renderNode = ({
    node,
    style,
    dragHandle,
  }: NodeRendererProps<TreeNode>) => {
    return (
      <Box
        style={style}
        sx={{
          bgcolor: node.isFocused ? blue[50] : "",
          display: "flex",
          alignItems: "center",
          paddingX: 1,
          minWidth: "max-content",
        }}
        ref={dragHandle}
        onClick={node.toggle}
      >
        {renderNodeLabel(node)}
      </Box>
    );
  };

  const { ref, width, height } = useResizeObserver();

  return (
    <Box ref={ref} sx={{ flex: 1, overflowX: "auto", display: "flex" }}>
      <Box sx={{ minWidth: "100%", width: "100%" }}>
        <Tree data={treeData} height={height} width={width}>
          {renderNode}
        </Tree>
      </Box>
    </Box>
  );
};

export default ResourceTree;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant