123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- import React, { memo } from "react";
- import { useTranslation } from "react-i18next";
- import styled from "styled-components";
- import debounce from "lodash.debounce";
- import { useItemActions } from "react-sync-board";
- import { search, uid } from "../../utils";
- import Chevron from "../../ui/Chevron";
- const StyledItemList = styled.ul`
- display: flex;
- flex-flow: row wrap;
- list-style: none;
- margin: 0;
- padding: 0;
- & li.group {
- background-color: rgba(0, 0, 0, 0.1);
- padding: 0 0.5em;
- flex-basis: 100%;
- }
- overflow: visible;
- `;
- const StyledItem = styled.li`
- display: block;
- padding: 0.5em;
- margin: 0.2em;
- cursor: pointer;
- opacity: 0.7;
- &:hover {
- opacity: 1;
- }
- & > div {
- display: flex;
- flex-direction: column;
- align-items: center;
- pointer-events: none;
- max-width: 80px;
- & > span {
- margin-top: 0.2em;
- text-align: center;
- max-width: 80px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- padding: 0.2em 0.5em;
- }
- }
- &:hover > div > span {
- z-index: 2;
- max-width: none;
- overflow: visible;
- background-color: #222;
- box-shadow: 0px 3px 6px #00000029;
- }
- `;
- const size = 60;
- const NewItem = memo(({ type, template, component: Component, name }) => {
- const { pushItem } = useItemActions();
- const addItem = React.useCallback(async () => {
- pushItem({
- ...(typeof template === "function" ? template() : template),
- id: uid(),
- type,
- });
- }, [pushItem, template, type]);
- return (
- <StyledItem onClick={addItem}>
- <div className="item-library__component">
- <Component
- {...(typeof template === "function" ? template() : template)}
- width={size}
- height={size}
- size={size}
- />
- <span>{name}</span>
- </div>
- </StyledItem>
- );
- });
- NewItem.displayName = "NewItem";
- const SubItemList = ({ name, items }) => {
- const { t } = useTranslation();
- const [open, setOpen] = React.useState(false);
- const { pushItems } = useItemActions();
- const addItems = React.useCallback(
- async (itemsToAdd) => {
- pushItems(
- itemsToAdd.map(({ template }) => ({
- ...(typeof template === "function" ? template() : template),
- id: uid(),
- }))
- );
- },
- [pushItems]
- );
- return (
- <>
- <h3
- onClick={() => setOpen((prev) => !prev)}
- style={{ cursor: "pointer" }}
- >
- {open ? (
- <Chevron orientation="bottom" color="#8c8c8c" />
- ) : (
- <Chevron color="#8c8c8c" />
- )}{" "}
- {name}{" "}
- <span
- style={{ fontSize: "0.6em" }}
- onClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
- addItems(items);
- }}
- >
- [{t("Add all")}]
- </span>
- </h3>
- {open && <ItemList items={items} />}
- </>
- );
- };
- const ItemList = ({ items }) => (
- <StyledItemList>
- {items.map((node) => {
- if (node.type) {
- return <NewItem {...node} key={node.uid} />;
- }
- // it's a group
- return (
- <li key={`group_${node.name}`} className="group">
- <SubItemList {...node} />
- </li>
- );
- })}
- </StyledItemList>
- );
- const MemoizedItemList = memo(ItemList);
- const filterItems = (filter, nodes) =>
- nodes.reduce((acc, node) => {
- if (node.type) {
- if (search(filter, node.name)) {
- acc.push(node);
- }
- return acc;
- }
- const filteredItems = filterItems(filter, node.items);
- if (filteredItems.length) {
- acc.push({ ...node, items: filteredItems });
- }
- return acc;
- }, []);
- const ItemLibrary = ({ items }) => {
- const { t } = useTranslation();
- const [filter, setFilter] = React.useState("");
- const [filteredItems, setFilteredItems] = React.useState(items);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- const debouncedFilterItems = React.useCallback(
- debounce((filterToApply, itemsToFilter) => {
- setFilteredItems(filterItems(filterToApply, itemsToFilter));
- }, 500),
- []
- );
- React.useEffect(() => {
- debouncedFilterItems(filter, items);
- }, [debouncedFilterItems, filter, items]);
- return (
- <>
- <input
- onChange={(e) => setFilter(e.target.value)}
- style={{ marginBottom: "1em" }}
- placeholder={t("Search...")}
- />
- <MemoizedItemList items={filteredItems} />
- </>
- );
- };
- export default memo(ItemLibrary);
|