feat: adopt Mantine 9.6 components (#6758)

This commit is contained in:
Thomas authored and GitHub committed 2026-09-03 18:21:51 +02:00
1 parent b3aa6155cc
commit 35155b18b9
28 files changed
+1192 -567

No files matched your search

@@ -1,21 +1,19 @@
"use client";
import {
ActionIcon,
ActionBar,
Avatar,
Button,
Divider,
Group,
HoverCard,
Menu,
Paper,
ScrollArea,
Stack,
Text,
Tooltip,
UnstyledButton,
} from "@mantine/core";
import { IconCheck, IconClipboard, IconCopy, IconFolderShare, IconTrash, IconX } from "@tabler/icons-react";
import { IconCheck, IconClipboard, IconCopy, IconFolderShare, IconTrash } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import { useCurrentLayout, useRequiredBoard } from "@homarr/boards/context";
@@ -129,24 +127,29 @@ export const BoardSelectionToolbar = () => {
];
return (
<Paper
<ActionBar
opened
onClose={clearSelection}
closeOnEscape
withBorder
shadow="xl"
radius="xl"
p="xs"
position={{ bottom: 28, left: 0, right: 0 }}
zIndex={1000}
aria-label={tSelection("summary", { count: sortedSelectedItems.length })}
bg="light-dark(var(--mantine-color-body), var(--mantine-color-dark-7))"
style={{
position: "fixed",
bottom: 28,
left: "50%",
transform: "translateX(-50%)",
zIndex: 1000,
width: "max-content",
maxWidth: "calc(100vw - 1rem)",
boxShadow: "0 12px 40px rgba(0, 0, 0, 0.28)",
backdropFilter: "blur(14px)",
border: "1px solid var(--mantine-color-default-border)",
animation: "slideUp 200ms cubic-bezier(0.4, 0, 0.2, 1) forwards",
}}
transitionProps={{
transition: "slide-up",
duration: 200,
timingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
}}
>
<Group gap="xs" wrap="nowrap">
@@ -220,7 +223,7 @@ export const BoardSelectionToolbar = () => {
</HoverCard.Dropdown>
</HoverCard>
<Divider orientation="vertical" />
<ActionBar.Divider />
{/* Actions */}
<Group gap="xs" wrap="nowrap" style={{ flexShrink: 0 }}>
@@ -279,18 +282,10 @@ export const BoardSelectionToolbar = () => {
</Button>
<Tooltip label={tSelection("clearShortcut")} withArrow>
<ActionIcon
size="sm"
variant="subtle"
color="gray"
onClick={clearSelection}
aria-label={tSelection("clear")}
>
<IconX size={14} />
</ActionIcon>
<ActionBar.CloseButton size="sm" variant="subtle" aria-label={tSelection("clear")} />
</Tooltip>
</Group>
</Group>
</Paper>
</ActionBar>
);
};
@@ -94,6 +94,11 @@ describe("Workshop widget install flow", () => {
expect(screenshots).toContain("1200x800");
expect(screenshots).toContain("zoomScreenshot");
expect(screenshots).toContain('import { Lightbox } from "@mantine/lightbox"');
expect(screenshots).toContain("withThumbnails");
expect(screenshots).toContain("withZoom");
expect(screenshots).toContain("withKeyboardEvents");
expect(screenshots).not.toMatch(/<Modal\b/u);
expect(detail).toContain("WorkshopScreenshots");
});
@@ -1,8 +1,9 @@
"use client";
import { useState } from "react";
import { ActionIcon, Box, Group, Image, Modal, Paper, Tooltip, UnstyledButton } from "@mantine/core";
import { ActionIcon, Box, Group, Image, Paper, Tooltip, UnstyledButton } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { Lightbox } from "@mantine/lightbox";
import { IconChevronLeft, IconChevronRight, IconZoomIn } from "@tabler/icons-react";
import { useI18n } from "@homarr/translation/client";
@@ -26,11 +27,18 @@ export function WorkshopScreenshots({ client, submissionId, title, screenshots }
if (screenshots.length === 0) return null;
const active = screenshots[Math.min(index, screenshots.length - 1)];
const currentIndex = Math.min(index, screenshots.length - 1);
const active = screenshots[currentIndex];
if (!active) return null;
const alt = (position: number) => t("screenshotAlt", { title, count: position + 1 });
const step = (delta: number) => setIndex((current) => (current + delta + screenshots.length) % screenshots.length);
const slides = screenshots.map((file, position) => ({
src: client.fileUrl(submissionId, file),
thumbSrc: client.fileUrl(submissionId, file, "192x128"),
alt: alt(position),
caption: alt(position),
}));
return (
<>
@@ -38,7 +46,7 @@ export function WorkshopScreenshots({ client, submissionId, title, screenshots }
<UnstyledButton onClick={zoomControls.open} w="100%" aria-label={t("zoomScreenshot")}>
<Image
src={client.fileUrl(submissionId, active, "1200x800")}
alt={alt(index)}
alt={alt(currentIndex)}
fit="contain"
h={{ base: 220, sm: 380 }}
bg="var(--mantine-color-default-hover)"
@@ -97,7 +105,7 @@ export function WorkshopScreenshots({ client, submissionId, title, screenshots }
key={file}
onClick={() => setIndex(position)}
aria-label={alt(position)}
aria-pressed={position === index}
aria-pressed={position === currentIndex}
>
<Box
w={96}
@@ -106,10 +114,10 @@ export function WorkshopScreenshots({ client, submissionId, title, screenshots }
overflow: "hidden",
borderRadius: "var(--mantine-radius-sm)",
outline:
position === index
position === currentIndex
? "2px solid var(--mantine-primary-color-filled)"
: "1px solid var(--mantine-color-default-border)",
opacity: position === index ? 1 : 0.65,
opacity: position === currentIndex ? 1 : 0.65,
}}
>
<Image src={client.fileUrl(submissionId, file, "192x128")} alt="" h={64} w={96} fit="cover" />
@@ -119,23 +127,30 @@ export function WorkshopScreenshots({ client, submissionId, title, screenshots }
</Group>
)}
<Modal
<Lightbox
opened={zoomOpened}
onClose={zoomControls.close}
size="auto"
centered
padding="xs"
title={alt(index)}
styles={{ body: { display: "flex", justifyContent: "center" } }}
>
<Image
src={client.fileUrl(submissionId, active)}
alt={alt(index)}
fit="contain"
mah="80dvh"
style={{ maxWidth: "min(90vw, 1400px)" }}
/>
</Modal>
slides={slides}
currentIndex={currentIndex}
onIndexChange={setIndex}
withNavigation={screenshots.length > 1}
withThumbnails={screenshots.length > 1}
withZoom
withKeyboardEvents
returnFocus
loop={screenshots.length > 1}
labels={{
lightboxLabel: t("screenshotViewer", { title }),
slideLabel: (position) => alt(position - 1),
slidesLabel: t("screenshotsLabel", { title }),
thumbnailLabel: (position) => alt(position - 1),
previousSlideLabel: t("previousScreenshot"),
nextSlideLabel: t("nextScreenshot"),
showThumbnailsLabel: t("showScreenshotThumbnails"),
hideThumbnailsLabel: t("hideScreenshotThumbnails"),
closeLabel: t("closeScreenshotViewer"),
}}
/>
</>
);
}