From 441cbbe71775e37bf19f44ad2ba4065e839fbfa8 Mon Sep 17 00:00:00 2001 From: Manuel <30572287+manuel-rw@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:59:33 +0100 Subject: [PATCH] feat: #1408 improve icon picker design (#1412) * feat: #1408 improve icon picker design * fix: formatting * fix: ui * feat: pr feedback --- apps/nextjs/public/images/apps/imdb.png | Bin 497 -> 0 bytes apps/nextjs/public/images/apps/imdb.svg | 3 + apps/nextjs/public/images/apps/tmdb.png | Bin 6578 -> 0 bytes apps/nextjs/public/images/apps/tmdb.svg | 1 + .../components/icons/picker/icon-picker.tsx | 95 +++++++++++------- packages/api/src/router/icons.ts | 2 +- .../radarr/radarr-integration.ts | 2 +- .../sonarr/sonarr-integration.ts | 2 +- packages/validation/src/icons.ts | 1 + 9 files changed, 67 insertions(+), 39 deletions(-) delete mode 100644 apps/nextjs/public/images/apps/imdb.png create mode 100644 apps/nextjs/public/images/apps/imdb.svg delete mode 100644 apps/nextjs/public/images/apps/tmdb.png create mode 100644 apps/nextjs/public/images/apps/tmdb.svg diff --git a/apps/nextjs/public/images/apps/imdb.png b/apps/nextjs/public/images/apps/imdb.png deleted file mode 100644 index 9565159a43cfe77978cc941ec36966c9541bca66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 497 zcmVgt_OOb^r2bgL@91!Eh`QP7x9nAK%V<*o6h8isYB}JIPY;Rk3l0qQK zpA(EhY#&=P9bg1>fUO}Lm<>wZxEuhB9-24+l$b#5z9L?%4yaA#fwL!;Afg}^7zLp& z9F#=?$N{a{5R2FM3SxBt3^Oq?{69Kh7#;$wEC{*EL~g + + \ No newline at end of file diff --git a/apps/nextjs/public/images/apps/tmdb.png b/apps/nextjs/public/images/apps/tmdb.png deleted file mode 100644 index 9f983b883fa15a3b34774844160e4fd1394b2d33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6578 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 diff --git a/apps/nextjs/public/images/apps/tmdb.svg b/apps/nextjs/public/images/apps/tmdb.svg new file mode 100644 index 000000000..42f31f154 --- /dev/null +++ b/apps/nextjs/public/images/apps/tmdb.svg @@ -0,0 +1 @@ +Asset 2 \ No newline at end of file diff --git a/apps/nextjs/src/components/icons/picker/icon-picker.tsx b/apps/nextjs/src/components/icons/picker/icon-picker.tsx index 91983ff7c..bd77a27d5 100644 --- a/apps/nextjs/src/components/icons/picker/icon-picker.tsx +++ b/apps/nextjs/src/components/icons/picker/icon-picker.tsx @@ -1,9 +1,23 @@ import type { FocusEventHandler } from "react"; -import { useState } from "react"; -import { Combobox, Group, Image, InputBase, Skeleton, Text, useCombobox } from "@mantine/core"; +import { startTransition, useState } from "react"; +import { + Box, + Card, + Combobox, + Flex, + Image, + Indicator, + InputBase, + Paper, + Skeleton, + Stack, + Text, + UnstyledButton, + useCombobox, +} from "@mantine/core"; import { clientApi } from "@homarr/api/client"; -import { useI18n, useScopedI18n } from "@homarr/translation/client"; +import { useScopedI18n } from "@homarr/translation/client"; interface IconPickerProps { initialValue?: string; @@ -18,10 +32,9 @@ export const IconPicker = ({ initialValue, onChange, error, onFocus, onBlur }: I const [search, setSearch] = useState(initialValue ?? ""); const [previewUrl, setPreviewUrl] = useState(initialValue ?? null); - const t = useI18n(); const tCommon = useScopedI18n("common"); - const { data, isFetching } = clientApi.icon.findIcons.useQuery({ + const [data] = clientApi.icon.findIcons.useSuspenseQuery({ searchText: search, }); @@ -29,39 +42,53 @@ export const IconPicker = ({ initialValue, onChange, error, onFocus, onBlur }: I onDropdownClose: () => combobox.resetSelectedOption(), }); - const notNullableData = data?.icons ?? []; - - const totalOptions = notNullableData.reduce((acc, group) => acc + group.icons.length, 0); - - const groups = notNullableData.map((group) => { + const totalOptions = data.icons.reduce((acc, group) => acc + group.icons.length, 0); + const groups = data.icons.map((group) => { const options = group.icons.map((item) => ( - - - - {item.name} - - + { + const value = item.url; + startTransition(() => { + setValue(value); + setPreviewUrl(value); + setSearch(value); + onChange(value); + combobox.closeDropdown(); + }); + }} + key={item.id} + > + + + + + + + + )); return ( - - {options} - + + + {group.slug} + + + {options} + + ); }); return ( - { - setValue(value); - setPreviewUrl(value); - setSearch(value); - onChange(value); - combobox.closeDropdown(); - }} - store={combobox} - withinPortal - > + } @@ -91,18 +118,14 @@ export const IconPicker = ({ initialValue, onChange, error, onFocus, onBlur }: I withAsterisk error={error} label={tCommon("iconPicker.label")} + placeholder={tCommon("iconPicker.header", { countIcons: data.countIcons })} /> - - {tCommon("iconPicker.header", { countIcons: data?.countIcons })} - {totalOptions > 0 ? ( - groups - ) : !isFetching ? ( - {t("search.nothingFound")} + {groups} ) : ( Array(15) .fill(0) diff --git a/packages/api/src/router/icons.ts b/packages/api/src/router/icons.ts index e99918da3..7b7c2f52b 100644 --- a/packages/api/src/router/icons.ts +++ b/packages/api/src/router/icons.ts @@ -16,7 +16,7 @@ export const iconsRouter = createTRPCRouter({ url: true, }, where: (input.searchText?.length ?? 0) > 0 ? like(icons.name, `%${input.searchText}%`) : undefined, - limit: 5, + limit: input.limitPerGroup, }, }, }), diff --git a/packages/integrations/src/media-organizer/radarr/radarr-integration.ts b/packages/integrations/src/media-organizer/radarr/radarr-integration.ts index a562ce2d0..bee8b1d63 100644 --- a/packages/integrations/src/media-organizer/radarr/radarr-integration.ts +++ b/packages/integrations/src/media-organizer/radarr/radarr-integration.ts @@ -76,7 +76,7 @@ export class RadarrIntegration extends Integration { name: "IMDb", color: "#f5c518", isDark: false, - logo: "/images/apps/imdb.png", + logo: "/images/apps/imdb.svg", }); } diff --git a/packages/integrations/src/media-organizer/sonarr/sonarr-integration.ts b/packages/integrations/src/media-organizer/sonarr/sonarr-integration.ts index 0b18e2b0a..faa789b1b 100644 --- a/packages/integrations/src/media-organizer/sonarr/sonarr-integration.ts +++ b/packages/integrations/src/media-organizer/sonarr/sonarr-integration.ts @@ -75,7 +75,7 @@ export class SonarrIntegration extends Integration { name: "IMDb", color: "#f5c518", isDark: false, - logo: "/images/apps/imdb.png", + logo: "/images/apps/imdb.svg", }); } diff --git a/packages/validation/src/icons.ts b/packages/validation/src/icons.ts index 031878b99..c621ecea5 100644 --- a/packages/validation/src/icons.ts +++ b/packages/validation/src/icons.ts @@ -2,6 +2,7 @@ import { z } from "zod"; const findIconsSchema = z.object({ searchText: z.string().optional(), + limitPerGroup: z.number().min(1).max(500).default(12), }); export const iconsSchemas = {