Add search spotlight with registration hook (#82)

* wip: add spotlight

* feat: add spotlight with registration hook and group chips

* chore: address pull request feedback

* docs: add documentation for usage of spotlight actions

* fix: deepsource issue JS-0415

* feat: add support for dependencies of spotlight actions

* fix: lockfile broken

* feat: add hover effect for spotlight action

* docs: Add documentation about dependency array

* refactor: remove test spotlight actions, disallow all as group for actions

* fix: type issues

* chore: address pull request feedback
This commit is contained in:
Meier Lukas
2024-02-17 14:11:23 +01:00
committed by GitHub
parent 3577bd6ac3
commit d5025da789
25 changed files with 2833 additions and 5243 deletions

View File

@@ -0,0 +1,8 @@
"use client";
import type { PropsWithChildren } from "react";
import { Provider } from "jotai";
export const JotaiProvider = ({ children }: PropsWithChildren) => {
return <Provider>{children}</Provider>;
};

View File

@@ -0,0 +1,18 @@
import React from "react";
type PropsWithChildren = Required<React.PropsWithChildren>;
export const composeWrappers = (
wrappers: React.FunctionComponent<PropsWithChildren>[],
): React.FunctionComponent<PropsWithChildren> => {
return wrappers
.reverse()
.reduce((Acc, Current): React.FunctionComponent<PropsWithChildren> => {
// eslint-disable-next-line react/display-name
return (props) => (
<Current>
<Acc {...props} />
</Current>
);
});
};

View File

@@ -30,7 +30,6 @@ export const InitUserForm = () => {
});
const handleSubmit = async (values: FormType) => {
console.log(values);
await mutateAsync(values, {
onSuccess: () => {
showSuccessNotification({

View File

@@ -12,9 +12,11 @@ import {
uiConfiguration,
} from "@homarr/ui";
import { JotaiProvider } from "./_client-providers/jotai";
import { ModalsProvider } from "./_client-providers/modals";
import { NextInternationalProvider } from "./_client-providers/next-international";
import { TRPCReactProvider } from "./_client-providers/trpc";
import { composeWrappers } from "./compose";
const fontSans = Inter({
subsets: ["latin"],
@@ -51,25 +53,32 @@ export default function Layout(props: {
}) {
const colorScheme = "dark";
const StackedProvider = composeWrappers([
(innerProps) => <JotaiProvider {...innerProps} />,
(innerProps) => <TRPCReactProvider {...innerProps} />,
(innerProps) => (
<NextInternationalProvider {...innerProps} locale={props.params.locale} />
),
(innerProps) => (
<MantineProvider
{...innerProps}
defaultColorScheme={colorScheme}
{...uiConfiguration}
/>
),
(innerProps) => <ModalsProvider {...innerProps} />,
]);
return (
<html lang="en" suppressHydrationWarning>
<head>
<ColorSchemeScript defaultColorScheme={colorScheme} />
</head>
<body className={["font-sans", fontSans.variable].join(" ")}>
<TRPCReactProvider>
<NextInternationalProvider locale={props.params.locale}>
<MantineProvider
defaultColorScheme={colorScheme}
{...uiConfiguration}
>
<ModalsProvider>
<Notifications />
{props.children}
</ModalsProvider>
</MantineProvider>
</NextInternationalProvider>
</TRPCReactProvider>
<StackedProvider>
<Notifications />
{props.children}
</StackedProvider>
</body>
</html>
);

View File

@@ -34,26 +34,28 @@ interface Props {
export const SectionContent = ({ items, refs }: Props) => {
return (
<>
{items.map((item) => (
<div
key={item.id}
className="grid-stack-item"
data-id={item.id}
gs-x={item.xOffset}
gs-y={item.yOffset}
gs-w={item.width}
gs-h={item.height}
gs-min-w={1}
gs-min-h={1}
gs-max-w={4}
gs-max-h={4}
ref={refs.items.current[item.id] as RefObject<HTMLDivElement>}
>
<Card className="grid-stack-item-content" withBorder>
<BoardItem item={item} />
</Card>
</div>
))}
{items.map((item) => {
return (
<div
key={item.id}
className="grid-stack-item"
data-id={item.id}
gs-x={item.xOffset}
gs-y={item.yOffset}
gs-w={item.width}
gs-h={item.height}
gs-min-w={1}
gs-min-h={1}
gs-max-w={4}
gs-max-h={4}
ref={refs.items.current[item.id] as RefObject<HTMLDivElement>}
>
<Card className="grid-stack-item-content" withBorder>
<BoardItem item={item} />
</Card>
</div>
);
})}
</>
);
};

View File

@@ -107,7 +107,7 @@ export const useGridstack = ({
// Add listener for moving items in config from one wrapper to another
currentGrid?.on("added", (_, nodes) => {
nodes.forEach((node) => onAdd(node));
nodes.forEach(onAdd);
});
return () => {
@@ -192,8 +192,6 @@ const useCssVariableConfiguration = ({
const widgetWidth = mainRef.current.clientWidth / sectionColumnCount;
// widget width is used to define sizes of gridstack items within global.scss
root?.style.setProperty("--gridstack-widget-width", widgetWidth.toString());
console.log("widgetWidth", widgetWidth);
console.log(gridRef.current);
gridRef.current?.cellHeight(widgetWidth);
// gridRef.current is required otherwise the cellheight is run on production as undefined
// eslint-disable-next-line react-hooks/exhaustive-deps

View File

@@ -1,11 +1,11 @@
import type { ReactNode } from "react";
import Link from "next/link";
import { Spotlight } from "@homarr/spotlight";
import { AppShellHeader, Group, UnstyledButton } from "@homarr/ui";
import { ClientBurger } from "./header/burger";
import { DesktopSearchInput, MobileSearchButton } from "./header/search";
import { ClientSpotlight } from "./header/spotlight";
import { UserButton } from "./header/user";
import { HomarrLogoWithTitle } from "./logo/homarr-logo";
@@ -38,7 +38,7 @@ export const MainHeader = ({ logo, actions, hasNavigation = true }: Props) => {
<UserButton />
</Group>
</Group>
<ClientSpotlight />
<Spotlight />
</AppShellHeader>
);
};

View File

@@ -1,6 +1,6 @@
"use client";
import { spotlight } from "@homarr/spotlight";
import { openSpotlight } from "@homarr/spotlight";
import { useScopedI18n } from "@homarr/translation/client";
import { IconSearch, TextInput, UnstyledButton } from "@homarr/ui";
@@ -17,7 +17,7 @@ export const DesktopSearchInput = () => {
w={400}
size="sm"
leftSection={<IconSearch size={20} stroke={1.5} />}
onClick={spotlight.open}
onClick={openSpotlight}
>
{t("placeholder")}
</TextInput>
@@ -26,7 +26,7 @@ export const DesktopSearchInput = () => {
export const MobileSearchButton = () => {
return (
<HeaderButton onClick={spotlight.open} className={classes.mobileSearch}>
<HeaderButton onClick={openSpotlight} className={classes.mobileSearch}>
<IconSearch size={20} stroke={1.5} />
</HeaderButton>
);

View File

@@ -1,22 +0,0 @@
"use client";
import { Spotlight } from "@homarr/spotlight";
import { useScopedI18n } from "@homarr/translation/client";
import { IconSearch } from "@homarr/ui";
export const ClientSpotlight = () => {
const t = useScopedI18n("common.search");
return (
<Spotlight
actions={[]}
nothingFound={t("nothingFound")}
highlightQuery
searchProps={{
leftSection: <IconSearch size={20} stroke={1.5} />,
placeholder: `${t("placeholder")}`,
}}
yOffset={12}
/>
);
};