diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_client.module.css b/apps/nextjs/src/app/[locale]/boards/(content)/_client.module.css
index 00ca097b5..b60ab124a 100644
--- a/apps/nextjs/src/app/[locale]/boards/(content)/_client.module.css
+++ b/apps/nextjs/src/app/[locale]/boards/(content)/_client.module.css
@@ -19,35 +19,11 @@
align-self: start;
box-sizing: border-box;
height: var(--board-gutter-viewport-height);
- padding-top: 24px;
- margin-top: -24px;
overflow: clip;
}
.gutter[data-board-editing="true"] {
- outline: 1px dashed var(--mantine-color-default-border);
- outline-offset: -1px;
+ outline: 0.0625rem solid var(--mantine-color-default-border);
+ outline-offset: -0.0625rem;
border-radius: var(--mantine-radius-md);
}
-
-.gutter[data-board-editing="true"]::before {
- position: sticky;
- top: 0;
- inset-inline-start: 0;
- z-index: 2;
- display: block;
- width: fit-content;
- height: 24px;
- padding-inline: var(--mantine-spacing-xs);
- margin-top: -24px;
- overflow: hidden;
- font-size: var(--mantine-font-size-xs);
- line-height: 24px;
- color: var(--mantine-color-dimmed);
- text-overflow: ellipsis;
- white-space: nowrap;
- pointer-events: none;
- content: attr(aria-label);
- background: color-mix(in srgb, var(--mantine-color-body) 88%, transparent);
- border-radius: var(--mantine-radius-sm);
-}
diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_client.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_client.tsx
index 7a0f7f40f..7c9ab7c4e 100644
--- a/apps/nextjs/src/app/[locale]/boards/(content)/_client.tsx
+++ b/apps/nextjs/src/app/[locale]/boards/(content)/_client.tsx
@@ -1,5 +1,6 @@
"use client";
+import type { PropsWithChildren } from "react";
import { Box, Paper, Stack, Text } from "@mantine/core";
import { IconBulb } from "@tabler/icons-react";
@@ -22,6 +23,7 @@ import {
import { ScaledBoardCanvas } from "~/components/board/layout/scaled-board-canvas";
import { BoardEmptySection } from "~/components/board/sections/empty-section";
import { BoardGridEditorBoundary } from "~/components/board/sections/grid/board-grid-editor-boundary";
+import { GridEditorRegistryProvider } from "~/components/board/sections/grid/grid-editor-registry";
import { BoardGridPortalHost } from "~/components/board/sections/grid/grid-portal-host";
import { BoardSectionCollapseProvider } from "~/components/board/sections/section-collapse";
import { BoardBackgroundVideo } from "~/components/layout/background";
@@ -31,6 +33,12 @@ import classes from "./_client.module.css";
const APP_SHELL_INLINE_PADDING = 32;
+const BoardSelectionGridProvider = ({ children }: PropsWithChildren) => (
+
+ {children}
+
+);
+
export const ClientBoard = () => {
const board = useRequiredBoard();
const t = useI18n("board.landmark");
@@ -68,7 +76,7 @@ export const ClientBoard = () => {
.join(" ");
const content = (
-
+
@@ -144,7 +152,7 @@ export const ClientBoard = () => {
-
+
);
if (representativeWidth === null) return content;
diff --git a/apps/nextjs/src/components/assistant/assistant-panel.module.css b/apps/nextjs/src/components/assistant/assistant-panel.module.css
index 1abba2121..a7e46f6ce 100644
--- a/apps/nextjs/src/components/assistant/assistant-panel.module.css
+++ b/apps/nextjs/src/components/assistant/assistant-panel.module.css
@@ -157,30 +157,12 @@
border-bottom: 1px solid var(--mantine-color-default-border);
}
-.panelIdentity {
- min-width: 0;
- flex: 1 1 auto;
-}
-
-.panelIdentityIcon {
- flex: 0 0 auto;
-}
-
-.panelIdentityTitle,
-.panelThreadTitle {
- max-width: 14rem;
-}
-
.panelActions {
min-width: 0;
flex: 0 0 auto;
margin-inline-start: auto;
}
-.newConversationButton {
- flex: 0 0 auto;
-}
-
.panelAction {
width: 2.625rem;
height: 2.625rem;
@@ -207,21 +189,6 @@
display: none;
}
- .newConversationButton {
- width: 2.625rem;
- max-width: 2.625rem;
- height: 2.625rem;
- padding-inline: 0.75rem;
- }
-
- .newConversationButton :global(.mantine-Button-section) {
- margin-inline: 0;
- }
-
- .newConversationButton :global(.mantine-Button-label) {
- display: none;
- }
-
.messages {
padding: 0.5rem 0.65rem 1rem;
}
@@ -277,8 +244,7 @@
@container assistant-widget (max-width: 22rem) {
.panelAction,
- .historyButton,
- .newConversationButton {
+ .historyButton {
width: 2.25rem;
max-width: 2.25rem;
height: 2.25rem;
@@ -301,10 +267,6 @@
.composerContextLabel {
display: none;
}
-
- .panelThreadTitle {
- display: none;
- }
}
@container assistant-widget (max-width: 18rem) {
diff --git a/apps/nextjs/src/components/assistant/assistant-panel.tsx b/apps/nextjs/src/components/assistant/assistant-panel.tsx
index bdeb8ed45..a8b8eea9b 100644
--- a/apps/nextjs/src/components/assistant/assistant-panel.tsx
+++ b/apps/nextjs/src/components/assistant/assistant-panel.tsx
@@ -2368,7 +2368,7 @@ const EmptyThread = () => {
-
+
@@ -2957,7 +2957,6 @@ const Composer = (props: ComposerProps) => {
) : (
void;
}
-const AssistantPanelIdentity = () => {
- const t = useI18n("assistant");
- const threadTitle = useAuiState((state) => state.threadListItem.title);
-
- return (
-
-
-
-
-
-
- {t("title")}
-
-
- {threadTitle ?? t("newConversation")}
-
-
-
- );
-};
-
export const AssistantConversationSurface = ({
isRunning,
pendingAction,
@@ -3212,23 +3190,20 @@ export const AssistantConversationSurface = ({
return (
-
- }
aria-label={t("newConversation")}
>
- {t("newConversation")}
-
+
+
{onExpand && (
diff --git a/apps/nextjs/src/components/assistant/assistant-spotlight.tsx b/apps/nextjs/src/components/assistant/assistant-spotlight.tsx
index f48888ccc..f82a52d39 100644
--- a/apps/nextjs/src/components/assistant/assistant-spotlight.tsx
+++ b/apps/nextjs/src/components/assistant/assistant-spotlight.tsx
@@ -16,7 +16,7 @@ const AssistantPromptDetail = () => {
const t = useI18n("assistant");
return (
-
+
diff --git a/apps/nextjs/src/components/assistant/assistant-widget.tsx b/apps/nextjs/src/components/assistant/assistant-widget.tsx
index 1b7acee3e..1fee8e930 100644
--- a/apps/nextjs/src/components/assistant/assistant-widget.tsx
+++ b/apps/nextjs/src/components/assistant/assistant-widget.tsx
@@ -137,7 +137,7 @@ const CompactAssistantWidget = ({
-
+
@@ -167,13 +167,7 @@ interface AssistantWidgetStateProps {
children?: ReactNode;
}
-const AssistantWidgetState = ({
- icon: Icon,
- title,
- description,
- color = "red",
- children,
-}: AssistantWidgetStateProps) => (
+const AssistantWidgetState = ({ icon: Icon, title, description, color, children }: AssistantWidgetStateProps) => (
diff --git a/apps/nextjs/src/components/board/items/item-move-modal.tsx b/apps/nextjs/src/components/board/items/item-move-modal.tsx
index cded56a68..c0beae1fd 100644
--- a/apps/nextjs/src/components/board/items/item-move-modal.tsx
+++ b/apps/nextjs/src/components/board/items/item-move-modal.tsx
@@ -11,6 +11,7 @@ import { useI18n } from "@homarr/translation/client";
import type { Board, ContainerSectionItem, SectionItem } from "~/app/[locale]/boards/_types";
import { getLayoutRowCount } from "../layout";
import { resolvePinnedGridCollisions } from "../sections/grid/dnd";
+import { useRegisteredGridEditors } from "../sections/grid/grid-editor-registry";
import { getSectionGridColumnCount, getSectionGridPlacements } from "../sections/grid/section-grid-placements";
import type { CommitSectionGridInput, SectionGridPlacement } from "../sections/grid/use-grid-layout-actions";
import { useGridLayoutActions } from "../sections/grid/use-grid-layout-actions";
@@ -57,26 +58,29 @@ export const ItemMoveModal = createModal(({ actions, innerProps }) =
const tContainer = useI18n("section.container");
const tMoveResize = useI18n("item.moveResize");
const { board, commitSectionGrids, currentLayoutId, entry, sourceSectionId } = innerProps;
+ const registeredGridEditors = useRegisteredGridEditors();
const minimumSize = useMemo(
() => getEntryMinimumSize(board, currentLayoutId, entry),
[board, currentLayoutId, entry],
);
- const targets = useMemo(
- () =>
- getMoveTargets(board, currentLayoutId, entry, {
- canvas: tLandmark("canvas"),
- container: tContainer("action.create"),
- leftRail: tLandmark("leftRail"),
- rightRail: tLandmark("rightRail"),
- numbered: (name, index) => tMoveResize("target.numbered", { name, index: String(index) }),
- located: (name, location, index) =>
- tMoveResize("target.located", {
- name,
- location,
- index: String(index),
- }),
- }),
- [board, currentLayoutId, entry, tContainer, tLandmark, tMoveResize],
+ const targets = getMoveTargets(
+ board,
+ currentLayoutId,
+ entry,
+ {
+ canvas: tLandmark("canvas"),
+ container: tContainer("action.create"),
+ leftRail: tLandmark("leftRail"),
+ rightRail: tLandmark("rightRail"),
+ numbered: (name, index) => tMoveResize("target.numbered", { name, index: String(index) }),
+ located: (name, location, index) =>
+ tMoveResize("target.located", {
+ name,
+ location,
+ index: String(index),
+ }),
+ },
+ registeredGridEditors,
);
const targetById = useMemo(() => new Map(targets.map((target) => [target.id, target])), [targets]);
const initialTarget = targetById.get(sourceSectionId) ?? targets[0];
@@ -252,6 +256,7 @@ export const getMoveTargets = (
layoutId: string,
entry: MovableEntry,
labels: MoveTargetLabels,
+ registeredGridEditors?: ReadonlyMap,
): MoveTarget[] => {
const minimum = getEntryMinimumSize(board, layoutId, entry);
@@ -260,13 +265,15 @@ export const getMoveTargets = (
const columnCount = getSectionColumnCount(board, layoutId, section.id);
const lane = getRootSectionLane(section.xOffset);
if (columnCount === 0) return [];
+ const registeredEditor = registeredGridEditors?.get(section.id);
+ if (lane !== "main" && registeredGridEditors && !registeredEditor) return [];
return minimum.width <= columnCount
? [
{
id: section.id,
name: lane === "left" ? labels.leftRail : lane === "right" ? labels.rightRail : labels.canvas,
columnCount,
- maxRowCount: getSectionMaxRowCount(board, layoutId, section.id),
+ maxRowCount: registeredEditor?.placementMaxRowCount ?? getSectionMaxRowCount(board, layoutId, section.id),
},
]
: [];
diff --git a/apps/nextjs/src/components/board/layout/README.md b/apps/nextjs/src/components/board/layout/README.md
index 12ac371d0..6eb9c412f 100644
--- a/apps/nextjs/src/components/board/layout/README.md
+++ b/apps/nextjs/src/components/board/layout/README.md
@@ -3,8 +3,8 @@
The board uses fixed logical units:
- one `1 × 1` item is exactly `200 × 200` logical pixels;
-- the gap is always `24` logical pixels, giving a `224` pixel track pitch;
-- multi-cell items include the gaps between their cells (`2 × 1` is `424 × 200`);
+- the gap matches Mantine's `sm` spacing at `12` logical pixels, giving a `212` pixel track pitch;
+- multi-cell items include the gaps between their cells (`2 × 1` is `412 × 200`);
- the canvas is zoomed once with one uniform scale derived from its available width.
Items keep the same logical dimensions at every viewport. The viewport reserves
diff --git a/apps/nextjs/src/components/board/layout/constants.ts b/apps/nextjs/src/components/board/layout/constants.ts
index 26e3f0765..e4f309700 100644
--- a/apps/nextjs/src/components/board/layout/constants.ts
+++ b/apps/nextjs/src/components/board/layout/constants.ts
@@ -3,7 +3,7 @@
* individual cards never change their internal layout at different viewports.
*/
export const LOGICAL_GRID_CELL_SIZE = 200;
-export const LOGICAL_GRID_GAP = 24;
+export const LOGICAL_GRID_GAP = 12;
export const LOGICAL_GRID_PITCH = LOGICAL_GRID_CELL_SIZE + LOGICAL_GRID_GAP;
export const COLLAPSED_SECTION_ROW_COUNT = 0.5;
diff --git a/apps/nextjs/src/components/board/layout/scaled-board-canvas.module.css b/apps/nextjs/src/components/board/layout/scaled-board-canvas.module.css
index 014c503b6..e0f3b6d65 100644
--- a/apps/nextjs/src/components/board/layout/scaled-board-canvas.module.css
+++ b/apps/nextjs/src/components/board/layout/scaled-board-canvas.module.css
@@ -23,8 +23,8 @@
* zoom and enlarges everything together.
*/
--mantine-scale: var(--board-canvas-ui-scale, 1);
- /* Keep the legacy card inset visually stable while the fixed canvas is zoomed. */
- --board-grid-item-inset: calc(10px * var(--board-canvas-inverse-scale, 1) - 12px);
+ /* Keep a subtle boundary inset without widening the visual gap too much. */
+ --board-grid-item-inset: calc(var(--mantine-spacing-xs) / 2);
--mantine-spacing-xs: calc(0.625rem * var(--board-canvas-ui-scale, 1));
--mantine-spacing-sm: calc(0.75rem * var(--board-canvas-ui-scale, 1));
--mantine-spacing-md: calc(1rem * var(--board-canvas-ui-scale, 1));
diff --git a/apps/nextjs/src/components/board/layout/test/geometry.spec.ts b/apps/nextjs/src/components/board/layout/test/geometry.spec.ts
index 3d46398bb..0db173bb6 100644
--- a/apps/nextjs/src/components/board/layout/test/geometry.spec.ts
+++ b/apps/nextjs/src/components/board/layout/test/geometry.spec.ts
@@ -9,18 +9,18 @@ import {
} from "../index";
describe("fixed dashboard geometry", () => {
- test("keeps a 1x1 item exactly 200px with a fixed 24px gap", () => {
+ test("keeps a 1x1 item exactly 200px with a fixed 12px gap", () => {
expect(getLogicalTrackSize(1)).toBe(200);
- expect(getLogicalTrackSize(0.5)).toBe(88);
- expect(getLogicalTrackSize(2)).toBe(424);
- expect(getLogicalTrackSize(3)).toBe(648);
+ expect(getLogicalTrackSize(0.5)).toBe(94);
+ expect(getLogicalTrackSize(2)).toBe(412);
+ expect(getLogicalTrackSize(3)).toBe(624);
expect(getLogicalItemStyle({ x: 1, y: 2, w: 2, h: 3 })).toEqual({
position: "absolute",
- left: 224,
- top: 448,
- width: 424,
- height: 648,
+ left: 212,
+ top: 424,
+ width: 412,
+ height: 624,
});
});
@@ -35,7 +35,7 @@ describe("fixed dashboard geometry", () => {
expect(getGridRowCountForVisualHeight(1200, 1)).toBe(6);
expect(getLogicalTrackSize(getGridRowCountForVisualHeight(1200, 1))).toBeGreaterThanOrEqual(1200);
- expect(getGridRowCountForVisualHeight(1200, 0.5)).toBe(11);
+ expect(getGridRowCountForVisualHeight(1200, 0.5)).toBe(12);
expect(getLogicalTrackSize(getGridRowCountForVisualHeight(1200, 0.5)) * 0.5).toBeGreaterThanOrEqual(1200);
});
});
diff --git a/apps/nextjs/src/components/board/sections/container-section.tsx b/apps/nextjs/src/components/board/sections/container-section.tsx
index b18777dba..1c21a311e 100644
--- a/apps/nextjs/src/components/board/sections/container-section.tsx
+++ b/apps/nextjs/src/components/board/sections/container-section.tsx
@@ -42,7 +42,18 @@ export const BoardContainerSection = ({ section }: Props) => {
const labelRight = isEditMode ? 48 : options.showOpenAll ? 40 : 8;
return (
-
+
{
styles={{
root: {
overflow: "visible",
- "--opacity": board.opacity / 100,
- "--border-color": options.borderColor || undefined,
},
}}
radius={board.itemRadius}
@@ -66,7 +75,7 @@ export const BoardContainerSection = ({ section }: Props) => {
)}
- {!isVisuallyCollapsed && !options.collapsible && options.showLabel && options.title && (
-
- {options.title}
-
- )}
{options.showOpenAll && !isEditMode && (
{
+ {!isVisuallyCollapsed && !options.collapsible && options.showLabel && options.title && (
+
+ {options.title}
+
+ )}
{isEditMode && }
);
diff --git a/apps/nextjs/src/components/board/sections/grid/board-grid-editor-boundary.tsx b/apps/nextjs/src/components/board/sections/grid/board-grid-editor-boundary.tsx
index ceb64c6de..0e081e0a1 100644
--- a/apps/nextjs/src/components/board/sections/grid/board-grid-editor-boundary.tsx
+++ b/apps/nextjs/src/components/board/sections/grid/board-grid-editor-boundary.tsx
@@ -7,7 +7,7 @@ import { createPortal } from "react-dom";
import { useEditMode } from "@homarr/boards/edit-mode";
import { getLoadedGridEditorModule, loadGridEditorAsync } from "./grid-editor-loader";
-import { GridEditorRegistryProvider, useRegisteredGridEditors } from "./grid-editor-registry";
+import { useRegisteredGridEditors } from "./grid-editor-registry";
import { GridEditorRuntimeProvider } from "./grid-editor-runtime";
type BoardGridEditorProviderComponent = ComponentType;
@@ -54,14 +54,14 @@ export const BoardGridEditorBoundary = ({ children }: PropsWithChildren) => {
const GridEditor = resolvedEditorModule?.default ?? null;
return (
-
+ <>
{children}
{isEditMode && Provider && GridEditor && (
)}
-
+ >
);
};
diff --git a/apps/nextjs/src/components/board/sections/grid/dnd/coordinates.spec.ts b/apps/nextjs/src/components/board/sections/grid/dnd/coordinates.spec.ts
index 5468f2d5d..93b387de7 100644
--- a/apps/nextjs/src/components/board/sections/grid/dnd/coordinates.spec.ts
+++ b/apps/nextjs/src/components/board/sections/grid/dnd/coordinates.spec.ts
@@ -23,7 +23,7 @@ describe("dnd grid coordinates", () => {
test("uses the dragged shape origin so a non-center grab retains its pointer offset", () => {
const scale = 0.75;
const target = { left: -280, top: 390, width: getLogicalTrackSize(5) * scale };
- const initialShape = { left: target.left, top: target.top, width: 424 * scale, height: 200 * scale };
+ const initialShape = { left: target.left, top: target.top, width: 412 * scale, height: 200 * scale };
const pointerDelta = { x: LOGICAL_GRID_PITCH * scale * 2, y: LOGICAL_GRID_PITCH * scale * 3 };
expect(
diff --git a/apps/nextjs/src/components/board/sections/grid/grid-editor-registry.tsx b/apps/nextjs/src/components/board/sections/grid/grid-editor-registry.tsx
index aa7839e65..09cba728d 100644
--- a/apps/nextjs/src/components/board/sections/grid/grid-editor-registry.tsx
+++ b/apps/nextjs/src/components/board/sections/grid/grid-editor-registry.tsx
@@ -55,6 +55,7 @@ export interface GridEditorRenderProps {
}
export interface RegisteredGridEditor extends GridEditorRenderProps {
+ placementMaxRowCount: number | null;
host: HTMLElement;
disabled: boolean;
}
diff --git a/apps/nextjs/src/components/board/sections/grid/grid-editor.css b/apps/nextjs/src/components/board/sections/grid/grid-editor.css
index 798a2c970..53079451f 100644
--- a/apps/nextjs/src/components/board/sections/grid/grid-editor.css
+++ b/apps/nextjs/src/components/board/sections/grid/grid-editor.css
@@ -155,17 +155,6 @@ body[data-board-grid-interacting="resize"] .board-grid-entry[data-dnd-active="tr
box-shadow: inset 0 0 0 2px var(--mantine-color-red-filled);
}
-[data-section-kind="empty"][data-rail-placement="main"] > .board-grid-editor[data-dnd-drop-target="true"] {
- outline: 2px solid color-mix(in srgb, var(--mantine-primary-color-filled) 60%, transparent);
- outline-offset: 4px;
- box-shadow: none;
-}
-
-[data-section-kind="empty"][data-rail-placement="main"]
- > .board-grid-editor[data-dnd-drop-target="true"][data-dnd-drop-valid="false"] {
- outline-color: var(--mantine-color-red-filled);
-}
-
[data-board-container-collapsed]
> [data-board-container-body]
> [data-section-id]
diff --git a/apps/nextjs/src/components/board/sections/grid/section-grid.module.css b/apps/nextjs/src/components/board/sections/grid/section-grid.module.css
index 12a8904d9..c08746422 100644
--- a/apps/nextjs/src/components/board/sections/grid/section-grid.module.css
+++ b/apps/nextjs/src/components/board/sections/grid/section-grid.module.css
@@ -50,6 +50,10 @@
overflow: visible;
}
+.staticItem:has([data-board-container-label]) {
+ z-index: 2;
+}
+
.editorEntry {
overflow: visible;
cursor: grab;
diff --git a/apps/nextjs/src/components/board/sections/grid/section-grid.tsx b/apps/nextjs/src/components/board/sections/grid/section-grid.tsx
index 959287b17..cec4616c9 100644
--- a/apps/nextjs/src/components/board/sections/grid/section-grid.tsx
+++ b/apps/nextjs/src/components/board/sections/grid/section-grid.tsx
@@ -130,6 +130,10 @@ export const SectionGrid = ({
const contentRowCount = Math.max(1, getLayoutRowCount(displayPlacements));
const rowCount = Math.max(contentRowCount, requestedRowCount, minimumViewportRowCount);
const maxRowCount = section.kind === "container" || railPlacement !== "main" ? rowCount : null;
+ let placementMaxRowCount = maxRowCount;
+ if (railPlacement !== "main") {
+ placementMaxRowCount = minimumViewportRowCount;
+ }
// A scrollable container isn't forced to grow with its content - it scrolls internally instead
// of expanding to fit every widget, so its viewport height is capped independently of rowCount.
const isScrollableContainer = section.kind === "container" && section.options.scrollable;
@@ -175,6 +179,7 @@ export const SectionGrid = ({
columnCount,
rowCount,
maxRowCount,
+ placementMaxRowCount,
placements: displayPlacements,
transactionPlacements: placements,
className: editorClassName,
@@ -190,6 +195,7 @@ export const SectionGrid = ({
entryElementStore,
isInteractionDisabled,
maxRowCount,
+ placementMaxRowCount,
placements,
rowCount,
section,
diff --git a/apps/nextjs/src/components/board/sections/item.module.css b/apps/nextjs/src/components/board/sections/item.module.css
index 319eb7b07..006e98e25 100644
--- a/apps/nextjs/src/components/board/sections/item.module.css
+++ b/apps/nextjs/src/components/board/sections/item.module.css
@@ -1,4 +1,4 @@
-.itemCard {
+.containerRoot {
@mixin dark {
--background-color: rgb(from var(--mantine-color-dark-6) r g b / var(--opacity));
--border-color: rgb(from var(--mantine-color-dark-4) r g b / var(--opacity));
@@ -7,6 +7,9 @@
--background-color: rgb(from var(--mantine-color-white) r g b / var(--opacity));
--border-color: rgb(from var(--mantine-color-gray-3) r g b / var(--opacity));
}
+}
+
+.itemCard {
background-color: var(--background-color) !important;
border-color: var(--border-color) !important;
}
@@ -42,6 +45,21 @@
white-space: nowrap;
}
+.containerLabel {
+ overflow: visible;
+ pointer-events: none;
+ background-color: var(--background-color);
+ border-color: var(--border-color);
+}
+
+.containerLabel :global(.mantine-Badge-label) {
+ min-width: 0;
+ overflow: hidden;
+ line-height: 1.2;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
.containerBody {
visibility: visible;
opacity: 1;
diff --git a/apps/nextjs/src/components/board/selection/board-selection-context.tsx b/apps/nextjs/src/components/board/selection/board-selection-context.tsx
index fb6f3ec6a..341017309 100644
--- a/apps/nextjs/src/components/board/selection/board-selection-context.tsx
+++ b/apps/nextjs/src/components/board/selection/board-selection-context.tsx
@@ -31,7 +31,7 @@ interface BoardSelectionContextValue {
copySelectedItems: () => Promise;
pasteItems: () => Promise;
removeSelectedItems: () => void;
- moveSelectedItemsToSection: (sectionId: string) => void;
+ moveSelectedItemsToSection: (sectionId: string, maxRowCount?: number | null) => void;
}
const BoardSelectionContext = createContext(null);
@@ -229,7 +229,7 @@ export const BoardSelectionProvider = ({ children }: PropsWithChildren) => {
}, [board.items, clearSelection, persistBoard, selectedItemIds, tCommon, tSelection]);
const moveSelectedItemsToSection = useCallback(
- (targetSectionId: string) => {
+ (targetSectionId: string, maxRowCount?: number | null) => {
if (selectedItemIds.size === 0) return;
const idsToMove = new Set(selectedItemIds);
@@ -248,7 +248,7 @@ export const BoardSelectionProvider = ({ children }: PropsWithChildren) => {
targetSection.kind === "container"
? (containerLayout?.width ?? 0)
: getBoardLaneColumnCount(boardLayout, getRootSectionLane(targetSection.xOffset));
- const rowCount = containerLayout?.height ?? 9999;
+ const rowCount = containerLayout?.height ?? maxRowCount ?? 9999;
if (columnCount === 0 || rowCount === 0) return previous;
const existingContainers = previous.sections
diff --git a/apps/nextjs/src/components/board/selection/board-selection-toolbar.tsx b/apps/nextjs/src/components/board/selection/board-selection-toolbar.tsx
index 4cd1fc118..c377ae166 100644
--- a/apps/nextjs/src/components/board/selection/board-selection-toolbar.tsx
+++ b/apps/nextjs/src/components/board/selection/board-selection-toolbar.tsx
@@ -25,6 +25,7 @@ import { useI18n } from "@homarr/translation/client";
import { widgetCatalogIcons } from "@homarr/widgets/catalog";
import type { EmptySection } from "~/app/[locale]/boards/_types";
+import { useRegisteredGridEditors } from "~/components/board/sections/grid/grid-editor-registry";
import { useBoardSelection } from "./board-selection-context";
const MAX_AVATARS_DISPLAYED = 5;
@@ -40,6 +41,7 @@ export const BoardSelectionToolbar = () => {
moveSelectedItemsToSection,
} = useBoardSelection();
const board = useRequiredBoard();
+ const registeredGridEditors = useRegisteredGridEditors();
const currentLayoutId = useCurrentLayout();
const currentLayout = board.layouts.find((layout) => layout.id === currentLayoutId);
const t = useI18n();
@@ -96,7 +98,15 @@ export const BoardSelectionToolbar = () => {
.at(0);
const placementOptions = [
- ...(mainCanvasSection ? [{ value: mainCanvasSection.id, label: t("item.create.destination.mainCanvas") }] : []),
+ ...(mainCanvasSection
+ ? [
+ {
+ value: mainCanvasSection.id,
+ label: t("item.create.destination.mainCanvas"),
+ maxRowCount: null,
+ },
+ ]
+ : []),
...board.sections.flatMap((section) => {
if (section.kind !== "empty") return [];
const lane = getRootSectionLane(section.xOffset);
@@ -105,6 +115,7 @@ export const BoardSelectionToolbar = () => {
{
value: section.id,
label: t(`item.create.destination.${lane === "left" ? "leftRail" : "rightRail"}`),
+ maxRowCount: registeredGridEditors.get(section.id)?.placementMaxRowCount,
},
];
}),
@@ -113,6 +124,7 @@ export const BoardSelectionToolbar = () => {
.map((section) => ({
value: section.id,
label: section.options.title.trim() || t("section.container.untitled"),
+ maxRowCount: section.layouts.find((layout) => layout.layoutId === currentLayoutId)?.height,
})),
];
@@ -244,7 +256,11 @@ export const BoardSelectionToolbar = () => {
{tSelection("moveTo")}
{placementOptions.map((opt) => (
- moveSelectedItemsToSection(opt.value)}>
+ moveSelectedItemsToSection(opt.value, opt.maxRowCount)}
+ >
{opt.label}
))}
diff --git a/packages/widgets/src/archive-team-warrior/component.tsx b/packages/widgets/src/archive-team-warrior/component.tsx
index d221338ef..81c1c64c4 100644
--- a/packages/widgets/src/archive-team-warrior/component.tsx
+++ b/packages/widgets/src/archive-team-warrior/component.tsx
@@ -82,7 +82,7 @@ const ArchiveTeamWarriorWidgetContent = ({
{(isAdvanced || options.showBroadcastMessage) && status.broadcastMessage && layout.showBroadcast && (
-
+
{status.broadcastMessage}
@@ -143,7 +143,13 @@ const ArchiveTeamWarriorWidgetContent = ({
{status.items.map((item) => {
const itemStatusKey = getStatusKey(item.status);
return (
-
+
@@ -201,6 +207,9 @@ export const getArchiveCompactLayout = (width: number, height: number, isAdvance
const formatBandwidth = (value?: number) => formatByteRate(Math.round(value ?? 0));
+const neutralSurfaceBorder =
+ "rgb(from var(--mantine-color-default-border) r g b / calc(var(--opacity, 1) * 0.45))";
+
type WarriorStatusKey = "running" | "completed" | "failed" | "canceled" | "stopped" | "idle" | "unknown";
const getStatusKey = (status?: string | null): WarriorStatusKey => {
diff --git a/packages/widgets/src/assistant/component.tsx b/packages/widgets/src/assistant/component.tsx
index 2f5f61cf3..242532d0d 100644
--- a/packages/widgets/src/assistant/component.tsx
+++ b/packages/widgets/src/assistant/component.tsx
@@ -17,7 +17,7 @@ export default function AssistantWidget(props: WidgetComponentProps<"assistant">
return (
-
+
diff --git a/packages/widgets/src/audio-stats/component.module.css b/packages/widgets/src/audio-stats/component.module.css
index 84b3a447c..4a16abfb6 100644
--- a/packages/widgets/src/audio-stats/component.module.css
+++ b/packages/widgets/src/audio-stats/component.module.css
@@ -40,7 +40,7 @@
gap: clamp(2px, 1.5cqi, 4px);
padding: clamp(4px, 2cqi, 8px);
border-radius: var(--mantine-radius-md);
- background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
+ background-color: transparent;
overflow: hidden;
}
diff --git a/packages/widgets/src/bazarr/component.module.css b/packages/widgets/src/bazarr/component.module.css
index f9f44256c..90c5ba4bd 100644
--- a/packages/widgets/src/bazarr/component.module.css
+++ b/packages/widgets/src/bazarr/component.module.css
@@ -29,7 +29,7 @@
gap: clamp(2px, 1.5cqi, 5px);
padding: clamp(4px, 2.5cqi, 10px);
border-radius: var(--mantine-radius-md);
- background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
+ background-color: transparent;
overflow: hidden;
}
diff --git a/packages/widgets/src/beszel-system-grid/component.module.css b/packages/widgets/src/beszel-system-grid/component.module.css
index b4854aa30..41bc5efed 100644
--- a/packages/widgets/src/beszel-system-grid/component.module.css
+++ b/packages/widgets/src/beszel-system-grid/component.module.css
@@ -1,7 +1,3 @@
-.progress {
- opacity: max(0.6, var(--opacity, 1));
-}
-
.clickableCard {
position: relative;
transition: background-color 150ms;
diff --git a/packages/widgets/src/beszel-system-grid/component.tsx b/packages/widgets/src/beszel-system-grid/component.tsx
index ae04f9ffb..260e33ed7 100644
--- a/packages/widgets/src/beszel-system-grid/component.tsx
+++ b/packages/widgets/src/beszel-system-grid/component.tsx
@@ -159,7 +159,6 @@ const MetricValue = ({ value, progress, size }: Pick
{progress && (