Files
excalidraw/packages/excalidraw/components/LibraryMenuSection.tsx
T
Archie Sengupta ec070911b8 feat: library search (#9903)
* feat(utils): add support for search input type in isWritableElement

* feat(i18n): add search text

* feat(cmdp+lib): add search functionality for command pallete and lib menu items

* chore: fix formats, and whitespaces

* fix: opt to optimal code changes

* chore: fix for linting

* focus input on mount

* tweak placeholder

* design and UX changes

* tweak item hover/active/seletected states

* unrelated: move publish button above delete/clear to keep it more stable

* esc to clear search input / close sidebar

* refactor command pallete library stuff

* make library commands bigger

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
2025-09-28 22:16:28 +02:00

83 lines
2.2 KiB
TypeScript

import React, { memo, useEffect, useState } from "react";
import type { ExcalidrawElement, NonDeleted } from "@excalidraw/element/types";
import { useTransition } from "../hooks/useTransition";
import { EmptyLibraryUnit, LibraryUnit } from "./LibraryUnit";
import type { SvgCache } from "../hooks/useLibraryItemSvg";
import type { LibraryItem } from "../types";
import type { ReactNode } from "react";
type LibraryOrPendingItem = readonly (
| LibraryItem
| /* pending library item */ {
id: null;
elements: readonly NonDeleted<ExcalidrawElement>[];
}
)[];
interface Props {
items: LibraryOrPendingItem;
onClick: (id: LibraryItem["id"] | null) => void;
onItemSelectToggle: (id: LibraryItem["id"], event: React.MouseEvent) => void;
onItemDrag: (id: LibraryItem["id"], event: React.DragEvent) => void;
isItemSelected: (id: LibraryItem["id"] | null) => boolean;
svgCache: SvgCache;
itemsRenderedPerBatch: number;
}
export const LibraryMenuSectionGrid = ({
children,
}: {
children: ReactNode;
}) => {
return <div className="library-menu-items-container__grid">{children}</div>;
};
export const LibraryMenuSection = memo(
({
items,
onItemSelectToggle,
onItemDrag,
isItemSelected,
onClick,
svgCache,
itemsRenderedPerBatch,
}: Props) => {
const [, startTransition] = useTransition();
const [index, setIndex] = useState(0);
useEffect(() => {
if (index < items.length) {
startTransition(() => {
setIndex(index + itemsRenderedPerBatch);
});
}
}, [index, items.length, startTransition, itemsRenderedPerBatch]);
return (
<>
{items.map((item, i) => {
return i < index ? (
<LibraryUnit
elements={item?.elements}
isPending={!item?.id && !!item?.elements}
onClick={onClick}
svgCache={svgCache}
id={item?.id}
selected={isItemSelected(item.id)}
onToggle={onItemSelectToggle}
onDrag={onItemDrag}
key={item?.id ?? i}
/>
) : (
<EmptyLibraryUnit key={i} />
);
})}
</>
);
},
);