chore: update prettier configuration for print width (#519)

* feat: update prettier configuration for print width

* chore: apply code formatting to entire repository

* fix: remove build files

* fix: format issue

---------

Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
Thomas Camlong
2024-05-19 22:38:39 +02:00
committed by GitHub
parent 919161798e
commit f1b1ec59ec
234 changed files with 2444 additions and 5375 deletions

View File

@@ -7,28 +7,16 @@ import type { AccordionProps } from "@mantine/core";
import { Accordion } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
type ActiveTabAccordionProps = PropsWithChildren<
Omit<AccordionProps<false>, "onChange">
>;
type ActiveTabAccordionProps = PropsWithChildren<Omit<AccordionProps<false>, "onChange">>;
// Replace state without fetchign new data
const replace = (newUrl: string) => {
window.history.replaceState(
{ ...window.history.state, as: newUrl, url: newUrl },
"",
newUrl,
);
window.history.replaceState({ ...window.history.state, as: newUrl, url: newUrl }, "", newUrl);
};
export const ActiveTabAccordion = ({
children,
...props
}: ActiveTabAccordionProps) => {
export const ActiveTabAccordion = ({ children, ...props }: ActiveTabAccordionProps) => {
const pathname = usePathname();
const onChange = useCallback(
(tab: string | null) => (tab ? replace(`?tab=${tab}`) : replace(pathname)),
[pathname],
);
const onChange = useCallback((tab: string | null) => (tab ? replace(`?tab=${tab}`) : replace(pathname)), [pathname]);
useShallowEffect(() => {
if (props.defaultValue) {

View File

@@ -47,12 +47,8 @@ export const useItemActions = () => {
({ kind }: CreateItem) => {
updateBoard((previous) => {
const lastSection = previous.sections
.filter(
(section): section is EmptySection => section.kind === "empty",
)
.sort(
(sectionA, sectionB) => sectionB.position - sectionA.position,
)[0];
.filter((section): section is EmptySection => section.kind === "empty")
.sort((sectionA, sectionB) => sectionB.position - sectionA.position)[0];
if (!lastSection) return previous;
@@ -91,8 +87,7 @@ export const useItemActions = () => {
...previous,
sections: previous.sections.map((section) => {
// Return same section if item is not in it
if (!section.items.some((item) => item.id === itemId))
return section;
if (!section.items.some((item) => item.id === itemId)) return section;
return {
...section,
items: section.items.map((item) => {
@@ -119,8 +114,7 @@ export const useItemActions = () => {
...previous,
sections: previous.sections.map((section) => {
// Return same section if item is not in it
if (!section.items.some((item) => item.id === itemId))
return section;
if (!section.items.some((item) => item.id === itemId)) return section;
return {
...section,
items: section.items.map((item) => {
@@ -128,9 +122,7 @@ export const useItemActions = () => {
if (item.id !== itemId) return item;
return {
...item,
...("integrations" in item
? { integrations: newIntegrations }
: {}),
...("integrations" in item ? { integrations: newIntegrations } : {}),
};
}),
};
@@ -168,18 +160,14 @@ export const useItemActions = () => {
const moveItemToSection = useCallback(
({ itemId, sectionId, ...positionProps }: MoveItemToSection) => {
updateBoard((previous) => {
const currentSection = previous.sections.find((section) =>
section.items.some((item) => item.id === itemId),
);
const currentSection = previous.sections.find((section) => section.items.some((item) => item.id === itemId));
// If item is in the same section (on initial loading) don't do anything
if (!currentSection) {
return previous;
}
const currentItem = currentSection.items.find(
(item) => item.id === itemId,
);
const currentItem = currentSection.items.find((item) => item.id === itemId);
if (!currentItem) {
return previous;
}

View File

@@ -13,14 +13,7 @@ export const ItemSelectModal = createModal<void>(({ actions }) => {
return (
<Grid>
{objectEntries(widgetImports).map(([key, value]) => {
return (
<WidgetItem
key={key}
kind={key}
definition={value.definition}
closeModal={actions.closeModal}
/>
);
return <WidgetItem key={key} kind={key} definition={value.definition} closeModal={actions.closeModal} />;
})}
</Grid>
);
@@ -56,13 +49,7 @@ const WidgetItem = ({
<Text lh={1.2} style={{ whiteSpace: "normal" }} ta="center">
{t(`widget.${kind}.name`)}
</Text>
<Text
lh={1.2}
style={{ whiteSpace: "normal" }}
size="xs"
ta="center"
c="dimmed"
>
<Text lh={1.2} style={{ whiteSpace: "normal" }} size="xs" ta="center" c="dimmed">
{t(`widget.${kind}.description`)}
</Text>
</Stack>

View File

@@ -15,62 +15,55 @@ interface InnerProps {
onSuccess?: (name: string) => void;
}
export const BoardRenameModal = createModal<InnerProps>(
({ actions, innerProps }) => {
const utils = clientApi.useUtils();
const t = useI18n();
const { mutate, isPending } = clientApi.board.renameBoard.useMutation({
onSettled() {
void utils.board.getBoardByName.invalidate({
name: innerProps.previousName,
});
void utils.board.getHomeBoard.invalidate();
},
});
const form = useZodForm(validation.board.rename.omit({ id: true }), {
initialValues: {
export const BoardRenameModal = createModal<InnerProps>(({ actions, innerProps }) => {
const utils = clientApi.useUtils();
const t = useI18n();
const { mutate, isPending } = clientApi.board.renameBoard.useMutation({
onSettled() {
void utils.board.getBoardByName.invalidate({
name: innerProps.previousName,
});
void utils.board.getHomeBoard.invalidate();
},
});
const form = useZodForm(validation.board.rename.omit({ id: true }), {
initialValues: {
name: innerProps.previousName,
},
});
const handleSubmit = (values: FormType) => {
mutate(
{
id: innerProps.id,
name: values.name,
},
});
const handleSubmit = (values: FormType) => {
mutate(
{
id: innerProps.id,
name: values.name,
{
onSuccess: () => {
actions.closeModal();
innerProps.onSuccess?.(values.name);
},
{
onSuccess: () => {
actions.closeModal();
innerProps.onSuccess?.(values.name);
},
},
);
};
return (
<form onSubmit={form.onSubmit(handleSubmit)}>
<Stack>
<TextInput
label={t("board.field.name.label")}
{...form.getInputProps("name")}
data-autofocus
/>
<Group justify="end">
<Button variant="subtle" color="gray" onClick={actions.closeModal}>
{t("common.action.cancel")}
</Button>
<Button type="submit" loading={isPending}>
{t("common.action.confirm")}
</Button>
</Group>
</Stack>
</form>
},
);
},
).withOptions({
defaultTitle: (t) =>
t("board.setting.section.dangerZone.action.rename.modal.title"),
};
return (
<form onSubmit={form.onSubmit(handleSubmit)}>
<Stack>
<TextInput label={t("board.field.name.label")} {...form.getInputProps("name")} data-autofocus />
<Group justify="end">
<Button variant="subtle" color="gray" onClick={actions.closeModal}>
{t("common.action.cancel")}
</Button>
<Button type="submit" loading={isPending}>
{t("common.action.confirm")}
</Button>
</Group>
</Stack>
</form>
);
}).withOptions({
defaultTitle: (t) => t("board.setting.section.dangerZone.action.rename.modal.title"),
});
type FormType = Omit<z.infer<(typeof validation)["board"]["rename"]>, "id">;

View File

@@ -2,9 +2,7 @@ import { auth } from "@homarr/auth/next";
import type { BoardPermissionsProps } from "@homarr/auth/shared";
import { constructBoardPermissions } from "@homarr/auth/shared";
export const getBoardPermissionsAsync = async (
board: BoardPermissionsProps,
) => {
export const getBoardPermissionsAsync = async (board: BoardPermissionsProps) => {
const session = await auth();
return constructBoardPermissions(board, session);
};

View File

@@ -1,12 +1,5 @@
import type { RefObject } from "react";
import {
Card,
Collapse,
Group,
Stack,
Title,
UnstyledButton,
} from "@mantine/core";
import { Card, Collapse, Group, Stack, Title, UnstyledButton } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { IconChevronDown, IconChevronUp } from "@tabler/icons-react";
@@ -30,23 +23,14 @@ export const BoardCategorySection = ({ section, mainRef }: Props) => {
<Group wrap="nowrap" gap="sm">
<UnstyledButton w="100%" p="sm" onClick={toggle}>
<Group wrap="nowrap">
{opened ? (
<IconChevronUp size={20} />
) : (
<IconChevronDown size={20} />
)}
{opened ? <IconChevronUp size={20} /> : <IconChevronDown size={20} />}
<Title order={3}>{section.name}</Title>
</Group>
</UnstyledButton>
<CategoryMenu category={section} />
</Group>
<Collapse in={opened} p="sm" pt={0}>
<div
className="grid-stack grid-stack-category"
data-category
data-section-id={section.id}
ref={refs.wrapper}
>
<div className="grid-stack grid-stack-category" data-category data-section-id={section.id} ref={refs.wrapper}>
<SectionContent items={section.items} refs={refs} />
</div>
</Collapse>

View File

@@ -2,11 +2,7 @@ import { useCallback } from "react";
import { createId } from "@homarr/db/client";
import type {
CategorySection,
EmptySection,
Section,
} from "~/app/[locale]/boards/_types";
import type { CategorySection, EmptySection, Section } from "~/app/[locale]/boards/_types";
import { useUpdateBoard } from "~/app/[locale]/boards/(content)/_client";
interface AddCategory {
@@ -41,9 +37,7 @@ export const useCategoryActions = () => {
sections: [
// Place sections before the new category
...previous.sections.filter(
(section) =>
(section.kind === "category" || section.kind === "empty") &&
section.position < position,
(section) => (section.kind === "category" || section.kind === "empty") && section.position < position,
),
{
id: createId(),
@@ -62,8 +56,7 @@ export const useCategoryActions = () => {
...previous.sections
.filter(
(section): section is CategorySection | EmptySection =>
(section.kind === "category" || section.kind === "empty") &&
section.position >= position,
(section.kind === "category" || section.kind === "empty") && section.position >= position,
)
.map((section) => ({
...section,
@@ -134,29 +127,19 @@ export const useCategoryActions = () => {
({ id, direction }: MoveCategory) => {
updateBoard((previous) => {
const currentCategory = previous.sections.find(
(section): section is CategorySection =>
section.kind === "category" && section.id === id,
(section): section is CategorySection => section.kind === "category" && section.id === id,
);
if (!currentCategory) return previous;
if (currentCategory?.position === 1 && direction === "up")
return previous;
if (
currentCategory?.position === previous.sections.length - 2 &&
direction === "down"
)
return previous;
if (currentCategory?.position === 1 && direction === "up") return previous;
if (currentCategory?.position === previous.sections.length - 2 && direction === "down") return previous;
return {
...previous,
sections: previous.sections.map((section) => {
if (section.kind !== "category" && section.kind !== "empty")
return section;
if (section.kind !== "category" && section.kind !== "empty") return section;
const offset = direction === "up" ? -2 : 2;
// Move category and empty section
if (
section.position === currentCategory.position ||
section.position - 1 === currentCategory.position
) {
if (section.position === currentCategory.position || section.position - 1 === currentCategory.position) {
return {
...section,
position: section.position + offset,
@@ -165,8 +148,7 @@ export const useCategoryActions = () => {
if (
direction === "up" &&
(section.position === currentCategory.position - 2 ||
section.position === currentCategory.position - 1)
(section.position === currentCategory.position - 2 || section.position === currentCategory.position - 1)
) {
return {
...section,
@@ -176,8 +158,7 @@ export const useCategoryActions = () => {
if (
direction === "down" &&
(section.position === currentCategory.position + 2 ||
section.position === currentCategory.position + 3)
(section.position === currentCategory.position + 2 || section.position === currentCategory.position + 3)
) {
return {
...section,
@@ -197,21 +178,18 @@ export const useCategoryActions = () => {
({ id: categoryId }: RemoveCategory) => {
updateBoard((previous) => {
const currentCategory = previous.sections.find(
(section): section is CategorySection =>
section.kind === "category" && section.id === categoryId,
(section): section is CategorySection => section.kind === "category" && section.id === categoryId,
);
if (!currentCategory) return previous;
const aboveWrapper = previous.sections.find(
(section): section is EmptySection =>
section.kind === "empty" &&
section.position === currentCategory.position - 1,
section.kind === "empty" && section.position === currentCategory.position - 1,
);
const removedWrapper = previous.sections.find(
(section): section is EmptySection =>
section.kind === "empty" &&
section.position === currentCategory.position + 1,
section.kind === "empty" && section.position === currentCategory.position + 1,
);
if (!aboveWrapper || !removedWrapper) return previous;
@@ -232,16 +210,10 @@ export const useCategoryActions = () => {
return {
...previous,
sections: [
...previous.sections.filter(
(section) => section.position < currentCategory.position - 1,
),
...previous.sections.filter((section) => section.position < currentCategory.position - 1),
{
...aboveWrapper,
items: [
...aboveWrapper.items,
...previousCategoryItems,
...previousBelowWrapperItems,
],
items: [...aboveWrapper.items, ...previousCategoryItems, ...previousBelowWrapperItems],
},
...previous.sections
.filter(

View File

@@ -16,41 +16,35 @@ interface InnerProps {
onSuccess: (category: Category) => void;
}
export const CategoryEditModal = createModal<InnerProps>(
({ actions, innerProps }) => {
const t = useI18n();
const form = useZodForm(z.object({ name: z.string().min(1) }), {
initialValues: {
name: innerProps.category.name,
},
});
export const CategoryEditModal = createModal<InnerProps>(({ actions, innerProps }) => {
const t = useI18n();
const form = useZodForm(z.object({ name: z.string().min(1) }), {
initialValues: {
name: innerProps.category.name,
},
});
return (
<form
onSubmit={form.onSubmit((values) => {
void innerProps.onSuccess({
...innerProps.category,
name: values.name,
});
actions.closeModal();
})}
>
<Stack>
<TextInput
label={t("section.category.field.name.label")}
data-autofocus
{...form.getInputProps("name")}
/>
<Group justify="right">
<Button onClick={actions.closeModal} variant="subtle" color="gray">
{t("common.action.cancel")}
</Button>
<Button type="submit" color="teal">
{innerProps.submitLabel}
</Button>
</Group>
</Stack>
</form>
);
},
).withOptions({});
return (
<form
onSubmit={form.onSubmit((values) => {
void innerProps.onSuccess({
...innerProps.category,
name: values.name,
});
actions.closeModal();
})}
>
<Stack>
<TextInput label={t("section.category.field.name.label")} data-autofocus {...form.getInputProps("name")} />
<Group justify="right">
<Button onClick={actions.closeModal} variant="subtle" color="gray">
{t("common.action.cancel")}
</Button>
<Button type="submit" color="teal">
{innerProps.submitLabel}
</Button>
</Group>
</Stack>
</form>
);
}).withOptions({});

View File

@@ -11,8 +11,7 @@ import { CategoryEditModal } from "./category-edit-modal";
export const useCategoryMenuActions = (category: CategorySection) => {
const { openModal } = useModalAction(CategoryEditModal);
const { openConfirmModal } = useConfirmModal();
const { addCategory, moveCategory, removeCategory, renameCategory } =
useCategoryActions();
const { addCategory, moveCategory, removeCategory, renameCategory } = useCategoryActions();
const t = useI18n();
const createCategoryAtPosition = useCallback(

View File

@@ -67,14 +67,8 @@ const useActions = (category: CategorySection) => {
};
const useEditModeActions = (category: CategorySection) => {
const {
addCategoryAbove,
addCategoryBelow,
moveCategoryUp,
moveCategoryDown,
edit,
remove,
} = useCategoryMenuActions(category);
const { addCategoryAbove, addCategoryBelow, moveCategoryUp, moveCategoryDown, edit, remove } =
useCategoryMenuActions(category);
return [
{

View File

@@ -5,12 +5,7 @@ import { useMemo } from "react";
import type { RefObject } from "react";
import { ActionIcon, Card, Menu } from "@mantine/core";
import { useElementSize } from "@mantine/hooks";
import {
IconDotsVertical,
IconLayoutKanban,
IconPencil,
IconTrash,
} from "@tabler/icons-react";
import { IconDotsVertical, IconLayoutKanban, IconPencil, IconTrash } from "@tabler/icons-react";
import combineClasses from "clsx";
import { useAtomValue } from "jotai";
@@ -43,12 +38,7 @@ export const SectionContent = ({ items, refs }: Props) => {
return (
<>
{items.map((item) => (
<BoardItem
key={item.id}
refs={refs}
item={item}
opacity={board.opacity}
/>
<BoardItem key={item.id} refs={refs} item={item} opacity={board.opacity} />
))}
</>
);
@@ -133,14 +123,9 @@ const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => {
const { openModal } = useModalAction(WidgetEditModal);
const { openConfirmModal } = useConfirmModal();
const isEditMode = useAtomValue(editModeAtom);
const { updateItemOptions, updateItemIntegrations, removeItem } =
useItemActions();
const { data: integrationData, isPending } =
clientApi.integration.all.useQuery();
const currentDefinition = useMemo(
() => widgetImports[item.kind].definition,
[item.kind],
);
const { updateItemOptions, updateItemIntegrations, removeItem } = useItemActions();
const { data: integrationData, isPending } = clientApi.integration.all.useQuery();
const currentDefinition = useMemo(() => widgetImports[item.kind].definition, [item.kind]);
if (!isEditMode || isPending) return null;
@@ -164,9 +149,7 @@ const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => {
integrationData: (integrationData ?? []).filter(
(integration) =>
"supportedIntegrations" in currentDefinition &&
(currentDefinition.supportedIntegrations as string[]).some(
(kind) => kind === integration.kind,
),
(currentDefinition.supportedIntegrations as string[]).some((kind) => kind === integration.kind),
),
integrationSupport: "supportedIntegrations" in currentDefinition,
});
@@ -185,34 +168,19 @@ const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => {
return (
<Menu withinPortal withArrow position="right-start" arrowPosition="center">
<Menu.Target>
<ActionIcon
variant="transparent"
pos="absolute"
top={offset}
right={offset}
style={{ zIndex: 1 }}
>
<ActionIcon variant="transparent" pos="absolute" top={offset} right={offset} style={{ zIndex: 1 }}>
<IconDotsVertical />
</ActionIcon>
</Menu.Target>
<Menu.Dropdown miw={128}>
<Menu.Label>{tItem("menu.label.settings")}</Menu.Label>
<Menu.Item
leftSection={<IconPencil size={16} />}
onClick={openEditModal}
>
<Menu.Item leftSection={<IconPencil size={16} />} onClick={openEditModal}>
{tItem("action.edit")}
</Menu.Item>
<Menu.Item leftSection={<IconLayoutKanban size={16} />}>
{tItem("action.move")}
</Menu.Item>
<Menu.Item leftSection={<IconLayoutKanban size={16} />}>{tItem("action.move")}</Menu.Item>
<Menu.Divider />
<Menu.Label c="red.6">{t("common.dangerZone")}</Menu.Label>
<Menu.Item
c="red.6"
leftSection={<IconTrash size={16} />}
onClick={openRemoveModal}
>
<Menu.Item c="red.6" leftSection={<IconTrash size={16} />} onClick={openRemoveModal}>
{tItem("action.remove")}
</Menu.Item>
</Menu.Dropdown>

View File

@@ -19,11 +19,7 @@ export const BoardEmptySection = ({ section, mainRef }: Props) => {
return (
<div
className={
section.items.length > 0 || isEditMode
? defaultClasses
: `${defaultClasses} gridstack-empty-wrapper`
}
className={section.items.length > 0 || isEditMode ? defaultClasses : `${defaultClasses} gridstack-empty-wrapper`}
style={{ transitionDuration: "0s" }}
data-empty
data-section-id={section.id}

View File

@@ -15,20 +15,14 @@ interface InitializeGridstackProps {
sectionColumnCount: number;
}
export const initializeGridstack = ({
section,
refs,
sectionColumnCount,
}: InitializeGridstackProps) => {
export const initializeGridstack = ({ section, refs, sectionColumnCount }: InitializeGridstackProps) => {
if (!refs.wrapper.current) return false;
// initialize gridstack
const newGrid = refs.gridstack;
newGrid.current = GridStack.init(
{
column: sectionColumnCount,
margin: Math.round(
Math.max(Math.min(refs.wrapper.current.offsetWidth / 100, 10), 1),
),
margin: Math.round(Math.max(Math.min(refs.wrapper.current.offsetWidth / 100, 10), 1)),
cellHeight: 128,
float: true,
alwaysShowResizeHandle: true,

View File

@@ -2,17 +2,10 @@ import type { MutableRefObject, RefObject } from "react";
import { createRef, useCallback, useEffect, useMemo, useRef } from "react";
import { useAtomValue } from "jotai";
import type {
GridItemHTMLElement,
GridStack,
GridStackNode,
} from "@homarr/gridstack";
import type { GridItemHTMLElement, GridStack, GridStackNode } from "@homarr/gridstack";
import type { Section } from "~/app/[locale]/boards/_types";
import {
useMarkSectionAsReady,
useRequiredBoard,
} from "~/app/[locale]/boards/(content)/_context";
import { useMarkSectionAsReady, useRequiredBoard } from "~/app/[locale]/boards/(content)/_context";
import { editModeAtom } from "../../editMode";
import { useItemActions } from "../../items/item-actions";
import { initializeGridstack } from "./init-gridstack";
@@ -32,10 +25,7 @@ interface UseGridstackProps {
mainRef?: RefObject<HTMLDivElement>;
}
export const useGridstack = ({
section,
mainRef,
}: UseGridstackProps): UseGristackReturnType => {
export const useGridstack = ({ section, mainRef }: UseGridstackProps): UseGristackReturnType => {
const isEditMode = useAtomValue(editModeAtom);
const markAsReady = useMarkSectionAsReady();
const { moveAndResizeItem, moveItemToSection } = useItemActions();
@@ -157,10 +147,7 @@ interface UseCssVariableConfiguration {
* @param mainRef reference to the main div wrapping all sections
* @param gridRef reference to the gridstack object
*/
const useCssVariableConfiguration = ({
mainRef,
gridRef,
}: UseCssVariableConfiguration) => {
const useCssVariableConfiguration = ({ mainRef, gridRef }: UseCssVariableConfiguration) => {
const board = useRequiredBoard();
// Get reference to the :root element
@@ -177,10 +164,7 @@ const useCssVariableConfiguration = ({
if (!mainRef?.current) return;
const widgetWidth = mainRef.current.clientWidth / board.columnCount;
// widget width is used to define sizes of gridstack items within global.scss
root?.style.setProperty(
"--gridstack-widget-width",
widgetWidth.toString(),
);
root?.style.setProperty("--gridstack-widget-width", widgetWidth.toString());
gridRef.current?.cellHeight(widgetWidth);
};
onResize();
@@ -194,9 +178,6 @@ const useCssVariableConfiguration = ({
// Define column count by using the sectionColumnCount
useEffect(() => {
root?.style.setProperty(
"--gridstack-column-count",
board.columnCount.toString(),
);
root?.style.setProperty("--gridstack-column-count", board.columnCount.toString());
}, [board.columnCount, root]);
};

View File

@@ -1,14 +1,6 @@
import type { FocusEventHandler } from "react";
import { useState } from "react";
import {
Combobox,
Group,
Image,
InputBase,
Skeleton,
Text,
useCombobox,
} from "@mantine/core";
import { Combobox, Group, Image, InputBase, Skeleton, Text, useCombobox } from "@mantine/core";
import { clientApi } from "@homarr/api/client";
import { useScopedI18n } from "@homarr/translation/client";
@@ -21,13 +13,7 @@ interface IconPickerProps {
onBlur?: FocusEventHandler;
}
export const IconPicker = ({
initialValue,
onChange,
error,
onFocus,
onBlur,
}: IconPickerProps) => {
export const IconPicker = ({ initialValue, onChange, error, onFocus, onBlur }: IconPickerProps) => {
const [value, setValue] = useState<string>(initialValue ?? "");
const [search, setSearch] = useState(initialValue ?? "");
@@ -43,10 +29,7 @@ export const IconPicker = ({
const notNullableData = data?.icons ?? [];
const totalOptions = notNullableData.reduce(
(acc, group) => acc + group.icons.length,
0,
);
const totalOptions = notNullableData.reduce((acc, group) => acc + group.icons.length, 0);
const groups = notNullableData.map((group) => {
const options = group.icons.map((item) => (
@@ -104,9 +87,7 @@ export const IconPicker = ({
<Combobox.Dropdown>
<Combobox.Header>
<Text c="dimmed">
{t("iconPicker.header", { countIcons: data?.countIcons })}
</Text>
<Text c="dimmed">{t("iconPicker.header", { countIcons: data?.countIcons })}</Text>
</Combobox.Header>
<Combobox.Options mah={350} style={{ overflowY: "auto" }}>
{totalOptions > 0 ? (
@@ -117,11 +98,7 @@ export const IconPicker = ({
Array(15)
.fill(0)
.map((_, index: number) => (
<Combobox.Option
value={`skeleton-${index}`}
key={index}
disabled
>
<Combobox.Option value={`skeleton-${index}`} key={index} disabled>
<Skeleton height={25} visible />
</Combobox.Option>
))

View File

@@ -51,11 +51,7 @@ export const LanguageCombobox = () => {
<Combobox.Options>
{supportedLanguages.map((languageKey) => (
<Combobox.Option value={languageKey} key={languageKey}>
<OptionItem
currentLocale={currentLocale}
localeKey={languageKey}
showCheck
/>
<OptionItem currentLocale={currentLocale} localeKey={languageKey} showCheck />
</Combobox.Option>
))}
</Combobox.Options>
@@ -76,9 +72,7 @@ const OptionItem = ({
return (
<Group wrap="nowrap" justify="space-between">
<Group wrap="nowrap">
<span
className={`fi fi-${localeAttributes[localeKey].flagIcon} ${classes.flagIcon}`}
></span>
<span className={`fi fi-${localeAttributes[localeKey].flagIcon} ${classes.flagIcon}`}></span>
<Group wrap="nowrap" gap="xs">
<Text>{localeAttributes[localeKey].name}</Text>
<Text size="xs" c="dimmed" inherit>
@@ -86,9 +80,7 @@ const OptionItem = ({
</Text>
</Group>
</Group>
{showCheck && localeKey === currentLocale && (
<IconCheck color="currentColor" size={16} />
)}
{showCheck && localeKey === currentLocale && <IconCheck color="currentColor" size={16} />}
</Group>
);
};

View File

@@ -4,10 +4,7 @@ import type { AppShellProps } from "@mantine/core";
import { useOptionalBoard } from "~/app/[locale]/boards/(content)/_context";
const supportedVideoFormats = ["mp4", "webm", "ogg"];
const isVideo = (url: string) =>
supportedVideoFormats.some((format) =>
url.toLowerCase().endsWith(`.${format}`),
);
const isVideo = (url: string) => supportedVideoFormats.some((format) => url.toLowerCase().endsWith(`.${format}`));
export const useOptionalBackgroundProps = (): Partial<AppShellProps> => {
const board = useOptionalBoard();

View File

@@ -26,13 +26,7 @@ export const MainHeader = ({ logo, actions, hasNavigation = true }: Props) => {
</UnstyledButton>
</Group>
<DesktopSearchInput />
<Group
h="100%"
align="center"
justify="end"
style={{ flex: 1 }}
wrap="nowrap"
>
<Group h="100%" align="center" justify="end" style={{ flex: 1 }} wrap="nowrap">
{actions}
<MobileSearchButton />
<UserButton />

View File

@@ -9,12 +9,7 @@ export const navigationCollapsedAtom = atom(true);
export const ClientBurger = () => {
const [collapsed, setCollapsed] = useAtom(navigationCollapsedAtom);
const toggle = useCallback(
() => setCollapsed((collapsed) => !collapsed),
[setCollapsed],
);
const toggle = useCallback(() => setCollapsed((collapsed) => !collapsed), [setCollapsed]);
return (
<Burger opened={!collapsed} onClick={toggle} hiddenFrom="sm" size="sm" />
);
return <Burger opened={!collapsed} onClick={toggle} hiddenFrom="sm" size="sm" />;
};

View File

@@ -23,24 +23,22 @@ const headerButtonActionIconProps: ActionIconProps = {
};
// eslint-disable-next-line react/display-name
export const HeaderButton = forwardRef<HTMLButtonElement, HeaderButtonProps>(
(props, ref) => {
if ("href" in props) {
return (
<ActionIcon
ref={ref as ForwardedRef<HTMLAnchorElement>}
component={Link}
{...props}
{...headerButtonActionIconProps}
>
{props.children}
</ActionIcon>
);
}
export const HeaderButton = forwardRef<HTMLButtonElement, HeaderButtonProps>((props, ref) => {
if ("href" in props) {
return (
<ActionIcon ref={ref} {...props} {...headerButtonActionIconProps}>
<ActionIcon
ref={ref as ForwardedRef<HTMLAnchorElement>}
component={Link}
{...props}
{...headerButtonActionIconProps}
>
{props.children}
</ActionIcon>
);
},
);
}
return (
<ActionIcon ref={ref} {...props} {...headerButtonActionIconProps}>
{props.children}
</ActionIcon>
);
});

View File

@@ -28,10 +28,7 @@ interface CommonLogoWithTitleProps {
hideTitleOnMobile?: boolean;
}
export const BoardLogoWithTitle = ({
size,
hideTitleOnMobile,
}: CommonLogoWithTitleProps) => {
export const BoardLogoWithTitle = ({ size, hideTitleOnMobile }: CommonLogoWithTitleProps) => {
const board = useRequiredBoard();
const imageOptions = useImageOptions();
return (

View File

@@ -14,16 +14,12 @@ const imageOptions = {
shouldUseNextImage: true,
};
export const HomarrLogo = ({ size }: LogoProps) => (
<Logo size={size} {...imageOptions} />
);
export const HomarrLogo = ({ size }: LogoProps) => <Logo size={size} {...imageOptions} />;
interface CommonLogoWithTitleProps {
size: LogoWithTitleProps["size"];
}
export const HomarrLogoWithTitle = ({ size }: CommonLogoWithTitleProps) => {
return (
<LogoWithTitle size={size} title={homarrPageTitle} image={imageOptions} />
);
return <LogoWithTitle size={size} title={homarrPageTitle} image={imageOptions} />;
};

View File

@@ -9,12 +9,7 @@ interface LogoProps {
shouldUseNextImage?: boolean;
}
export const Logo = ({
size = 60,
shouldUseNextImage = false,
src,
alt,
}: LogoProps) =>
export const Logo = ({ size = 60, shouldUseNextImage = false, src, alt }: LogoProps) =>
shouldUseNextImage ? (
<Image src={src} alt={alt} width={size} height={size} />
) : (
@@ -36,22 +31,13 @@ export interface LogoWithTitleProps {
hideTitleOnMobile?: boolean;
}
export const LogoWithTitle = ({
size,
title,
image,
hideTitleOnMobile,
}: LogoWithTitleProps) => {
export const LogoWithTitle = ({ size, title, image, hideTitleOnMobile }: LogoWithTitleProps) => {
const { logoSize, titleOrder } = logoWithTitleSizes[size];
return (
<Group gap="xs" wrap="nowrap">
<Logo {...image} size={logoSize} />
<Title
order={titleOrder}
visibleFrom={hideTitleOnMobile ? "sm" : undefined}
textWrap="nowrap"
>
<Title order={titleOrder} visibleFrom={hideTitleOnMobile ? "sm" : undefined} textWrap="nowrap">
{title}
</Title>
</Group>

View File

@@ -6,11 +6,7 @@ import { usePathname } from "next/navigation";
import { NavLink } from "@mantine/core";
export const CommonNavLink = (props: ClientNavigationLink) =>
"href" in props ? (
<NavLinkHref {...props} />
) : (
<NavLinkWithItems {...props} />
);
"href" in props ? <NavLinkHref {...props} /> : <NavLinkWithItems {...props} />;
const NavLinkHref = (props: NavigationLinkHref) => {
const pathname = usePathname();

View File

@@ -11,11 +11,7 @@ interface MainNavigationProps {
links: NavigationLink[];
}
export const MainNavigation = ({
headerSection,
footerSection,
links,
}: MainNavigationProps) => {
export const MainNavigation = ({ headerSection, footerSection, links }: MainNavigationProps) => {
return (
<AppShellNavbar p="md">
{headerSection && <AppShellSection>{headerSection}</AppShellSection>}

View File

@@ -12,50 +12,41 @@ interface InnerProps {
onSuccess: ({ name }: { name: string }) => Promise<void>;
}
export const AddBoardModal = createModal<InnerProps>(
({ actions, innerProps }) => {
const t = useI18n();
const form = useZodForm(
z.object({
name: boardSchemas.byName.shape.name.refine(
(value) => !innerProps.boardNames.includes(value),
{
params: createCustomErrorParams("boardAlreadyExists"),
},
),
export const AddBoardModal = createModal<InnerProps>(({ actions, innerProps }) => {
const t = useI18n();
const form = useZodForm(
z.object({
name: boardSchemas.byName.shape.name.refine((value) => !innerProps.boardNames.includes(value), {
params: createCustomErrorParams("boardAlreadyExists"),
}),
{
initialValues: {
name: "",
},
}),
{
initialValues: {
name: "",
},
);
},
);
return (
<form
onSubmit={form.onSubmit((values) => {
void innerProps.onSuccess(values);
actions.closeModal();
})}
>
<Stack>
<TextInput
label={t("board.field.name.label")}
data-autofocus
{...form.getInputProps("name")}
/>
<Group justify="right">
<Button onClick={actions.closeModal} variant="subtle" color="gray">
{t("common.action.cancel")}
</Button>
<Button disabled={!form.isValid()} type="submit" color="teal">
{t("common.action.create")}
</Button>
</Group>
</Stack>
</form>
);
},
).withOptions({
return (
<form
onSubmit={form.onSubmit((values) => {
void innerProps.onSuccess(values);
actions.closeModal();
})}
>
<Stack>
<TextInput label={t("board.field.name.label")} data-autofocus {...form.getInputProps("name")} />
<Group justify="right">
<Button onClick={actions.closeModal} variant="subtle" color="gray">
{t("common.action.cancel")}
</Button>
<Button disabled={!form.isValid()} type="submit" color="teal">
{t("common.action.create")}
</Button>
</Group>
</Stack>
</form>
);
}).withOptions({
defaultTitle: (t) => t("management.page.board.action.new.label"),
});

View File

@@ -1,13 +1,5 @@
import { Fragment } from "react";
import {
Card,
CardSection,
Divider,
Group,
Stack,
Text,
Title,
} from "@mantine/core";
import { Card, CardSection, Divider, Group, Stack, Text, Title } from "@mantine/core";
import { getI18n } from "@homarr/translation/server";
@@ -49,11 +41,7 @@ interface DangerZoneItemProps {
action: React.ReactNode;
}
export const DangerZoneItem = ({
label,
description,
action,
}: DangerZoneItemProps) => {
export const DangerZoneItem = ({ label, description, action }: DangerZoneItemProps) => {
return (
<Group justify="space-between" px="md">
<Stack gap={0}>

View File

@@ -4,13 +4,7 @@ import type { ReactNode } from "react";
import { useCallback, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
Center,
Menu,
Stack,
Text,
useMantineColorScheme,
} from "@mantine/core";
import { Center, Menu, Stack, Text, useMantineColorScheme } from "@mantine/core";
import { useTimeout } from "@mantine/hooks";
import {
IconCheck,
@@ -41,8 +35,7 @@ export const UserAvatarMenu = ({ children }: UserAvatarMenuProps) => {
const ColorSchemeIcon = colorScheme === "dark" ? IconSun : IconMoon;
const colorSchemeText =
colorScheme === "dark" ? t("switchToLightMode") : t("switchToDarkMode");
const colorSchemeText = colorScheme === "dark" ? t("switchToLightMode") : t("switchToDarkMode");
const session = useSession();
const router = useRouter();
@@ -63,17 +56,10 @@ export const UserAvatarMenu = ({ children }: UserAvatarMenuProps) => {
return (
<Menu width={300} withArrow withinPortal>
<Menu.Dropdown>
<Menu.Item
onClick={toggleColorScheme}
leftSection={<ColorSchemeIcon size="1rem" />}
>
<Menu.Item onClick={toggleColorScheme} leftSection={<ColorSchemeIcon size="1rem" />}>
{colorSchemeText}
</Menu.Item>
<Menu.Item
component={Link}
href="/boards"
leftSection={<IconHome size="1rem" />}
>
<Menu.Item component={Link} href="/boards" leftSection={<IconHome size="1rem" />}>
{t("homeBoard")}
</Menu.Item>
<Menu.Divider />
@@ -92,29 +78,18 @@ export const UserAvatarMenu = ({ children }: UserAvatarMenuProps) => {
{t("preferences")}
</Menu.Item>
<Menu.Item
component={Link}
href="/manage"
leftSection={<IconTool size="1rem" />}
>
<Menu.Item component={Link} href="/manage" leftSection={<IconTool size="1rem" />}>
{t("management")}
</Menu.Item>
</>
)}
<Menu.Divider />
{session.status === "authenticated" ? (
<Menu.Item
onClick={handleSignout}
leftSection={<IconLogout size="1rem" />}
color="red"
>
<Menu.Item onClick={handleSignout} leftSection={<IconLogout size="1rem" />} color="red">
{t("logout")}
</Menu.Item>
) : (
<Menu.Item
onClick={() => router.push("/auth/login")}
leftSection={<IconLogin size="1rem" />}
>
<Menu.Item onClick={() => router.push("/auth/login")} leftSection={<IconLogin size="1rem" />}>
{t("login")}
</Menu.Item>
)}
@@ -124,30 +99,28 @@ export const UserAvatarMenu = ({ children }: UserAvatarMenuProps) => {
);
};
const LogoutModal = createModal<{ onTimeout: () => void }>(
({ actions, innerProps }) => {
const t = useScopedI18n("common.userAvatar.menu");
const { start } = useTimeout(() => {
actions.closeModal();
innerProps.onTimeout();
}, 1500);
const LogoutModal = createModal<{ onTimeout: () => void }>(({ actions, innerProps }) => {
const t = useScopedI18n("common.userAvatar.menu");
const { start } = useTimeout(() => {
actions.closeModal();
innerProps.onTimeout();
}, 1500);
useEffect(() => {
start();
}, [start]);
useEffect(() => {
start();
}, [start]);
return (
<Center h={200 - 2 * 16}>
<Stack align="center" c="green">
<IconCheck size={50} />
<Text ta="center" fw="bold">
{t("loggedOut")}
</Text>
</Stack>
</Center>
);
},
).withOptions({
return (
<Center h={200 - 2 * 16}>
<Stack align="center" c="green">
<IconCheck size={50} />
<Text ta="center" fw="bold">
{t("loggedOut")}
</Text>
</Stack>
</Center>
);
}).withOptions({
centered: true,
withCloseButton: false,
transitionProps: {

View File

@@ -17,17 +17,7 @@ export const UserAvatar = async ({ size }: UserAvatarProps) => {
if (!currentSession?.user) return <Avatar {...commonProps} />;
if (currentSession.user.image)
return (
<Avatar
{...commonProps}
src={currentSession.user.image}
alt={currentSession.user.name!}
/>
);
return <Avatar {...commonProps} src={currentSession.user.image} alt={currentSession.user.name!} />;
return (
<Avatar {...commonProps}>
{currentSession.user.name!.substring(0, 2).toUpperCase()}
</Avatar>
);
return <Avatar {...commonProps}>{currentSession.user.name!.substring(0, 2).toUpperCase()}</Avatar>;
};