From 62da953356ff76694ddc50980a2b879a01bcbdd6 Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Fri, 10 Jan 2025 14:46:39 +0100 Subject: [PATCH] feat(items): add search to selection (#1887) --- .../board/items/item-select-modal.tsx | 97 +++++++++++++------ packages/translation/src/lang/en.json | 1 + 2 files changed, 67 insertions(+), 31 deletions(-) diff --git a/apps/nextjs/src/components/board/items/item-select-modal.tsx b/apps/nextjs/src/components/board/items/item-select-modal.tsx index 3789ebca3..a2fce1264 100644 --- a/apps/nextjs/src/components/board/items/item-select-modal.tsx +++ b/apps/nextjs/src/components/board/items/item-select-modal.tsx @@ -1,21 +1,67 @@ -import { Button, Card, Center, Grid, Stack, Text } from "@mantine/core"; +import { useMemo, useState } from "react"; +import { Button, Card, Center, Grid, Input, Stack, Text } from "@mantine/core"; +import { IconSearch } from "@tabler/icons-react"; import { objectEntries } from "@homarr/common"; import type { WidgetKind } from "@homarr/definitions"; import { createModal } from "@homarr/modals"; import { useI18n } from "@homarr/translation/client"; +import type { TablerIcon } from "@homarr/ui"; import { widgetImports } from "@homarr/widgets"; -import type { WidgetDefinition } from "@homarr/widgets"; import { useItemActions } from "./item-actions"; export const ItemSelectModal = createModal(({ actions }) => { + const [search, setSearch] = useState(""); + const t = useI18n(); + const { createItem } = useItemActions(); + + const items = useMemo( + () => + objectEntries(widgetImports) + .map(([kind, value]) => ({ + kind, + icon: value.definition.icon, + name: t(`widget.${kind}.name`), + description: t(`widget.${kind}.description`), + })) + .sort((itemA, itemB) => itemA.name.localeCompare(itemB.name)), + [t], + ); + + const filteredItems = useMemo( + () => items.filter((item) => item.name.toLowerCase().includes(search.toLowerCase())), + [items, search], + ); + + const handleAdd = (kind: WidgetKind) => { + createItem({ kind }); + actions.closeModal(); + }; + return ( - - {objectEntries(widgetImports).map(([key, value]) => { - return ; - })} - + + setSearch(event.currentTarget.value)} + leftSection={} + placeholder={`${t("item.create.search")}...`} + data-autofocus + onKeyDown={(event) => { + // Add item if there is only one item in the list and user presses Enter + if (event.key === "Enter" && filteredItems.length === 1) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + handleAdd(filteredItems[0]!.kind); + } + }} + /> + + + {filteredItems.map((item) => ( + handleAdd(item.kind)} /> + ))} + + ); }).withOptions({ defaultTitle: (t) => t("item.create.title"), @@ -23,20 +69,18 @@ export const ItemSelectModal = createModal(({ actions }) => { }); const WidgetItem = ({ - kind, - definition, - closeModal, + item, + onSelect, }: { - kind: WidgetKind; - definition: WidgetDefinition; - closeModal: () => void; + item: { + kind: WidgetKind; + name: string; + description: string; + icon: TablerIcon; + }; + onSelect: () => void; }) => { const t = useI18n(); - const { createItem } = useItemActions(); - const handleAdd = (kind: WidgetKind) => { - createItem({ kind }); - closeModal(); - }; return ( @@ -44,25 +88,16 @@ const WidgetItem = ({
- +
- {t(`widget.${kind}.name`)} + {item.name} - {t(`widget.${kind}.description`)} + {item.description}
-
diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index 8072ad1ee..0f7f627c2 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -966,6 +966,7 @@ }, "create": { "title": "Choose item to add", + "search": "Filter items", "addToBoard": "Add to board" }, "moveResize": {