fix: decouple responsive layout roles from breakpoints

* fix: rework responsive layout breakpoints

* fix: keep widget edit overlays visible

* fix: inset widget edit action

* fix: use valid widget inset token
This commit is contained in:
Thomas authored and GitHub committed 2026-08-28 17:11:51 +02:00
1 parent f455e71be5
commit aa179f1ec0
15 files changed
+56 -142

No files matched your search

@@ -40,6 +40,8 @@ interface Props {
saveSettingsAsync: () => Promise<FormValues | null>;
}
const layoutRoleOrder = { base: 0, custom: 1, mobile: 2 } as const;
export const LayoutSettingsContent = ({ board, form, isSaving, saveSettingsAsync }: Props) => {
const tBoard = useI18n("board");
const tLayout = useI18n("layout");
@@ -101,6 +103,9 @@ export const LayoutSettingsContent = ({ board, form, isSaving, saveSettingsAsync
const nextBreakpoint = getNextCustomBreakpoint(form.values.layouts);
const baseLayout = form.values.layouts.find((layout) => layout.role === "base");
const displayedLayouts = form.values.layouts
.map((layout, index) => ({ layout, index }))
.toSorted((first, second) => layoutRoleOrder[first.layout.role] - layoutRoleOrder[second.layout.role]);
return (
<SectionCard title={tBoard("setting.section.layout.title")}>
@@ -134,13 +139,9 @@ export const LayoutSettingsContent = ({ board, form, isSaving, saveSettingsAsync
</Button>
</Group>
{form.values.layouts.map((layout, index) => {
{displayedLayouts.map(({ layout, index }) => {
const persistedLayout = board.layouts.find((candidate) => candidate.id === layout.id);
const sourceLayout = persistedLayout ?? board.layouts.find((candidate) => candidate.role === "base");
const customBreakpoints = form.values.layouts
.filter((candidate) => candidate.role === "custom" && candidate.id !== layout.id)
.map((candidate) => candidate.breakpoint);
const baseBreakpoint = form.values.layouts.find((candidate) => candidate.role === "base")?.breakpoint ?? 768;
return (
<Fieldset
@@ -190,12 +191,8 @@ export const LayoutSettingsContent = ({ board, form, isSaving, saveSettingsAsync
}
disabled={layout.role === "mobile"}
styles={{ description: { color: "var(--mantine-color-text)" } }}
min={
layout.role === "base"
? Math.max(1, ...customBreakpoints.map((breakpoint) => breakpoint + 1))
: 1
}
max={layout.role === "custom" ? baseBreakpoint - 1 : 32767}
min={layout.role === "mobile" ? 0 : 1}
max={32767}
/>
</Stack>
</Grid.Col>
@@ -1,7 +1,7 @@
.settingsButton {
position: absolute;
top: -34px;
right: 14px;
top: calc(var(--mantine-spacing-sm) / 2);
right: calc(var(--mantine-spacing-sm) / 2);
z-index: 201;
opacity: 0;
pointer-events: none;
@@ -52,7 +52,6 @@
@media (max-width: 768px) {
.settingsButton {
right: 4px;
opacity: 1;
pointer-events: auto;
}
@@ -49,12 +49,14 @@
}
.widgetIcon {
width: var(--mantine-font-size-sm);
height: var(--mantine-font-size-sm);
flex-shrink: 0;
color: var(--mantine-color-dimmed);
}
.name {
font-size: 11px;
font-size: var(--mantine-font-size-xs);
font-weight: 600;
line-height: 1.2;
white-space: nowrap;
@@ -94,7 +96,7 @@
padding: 2px 6px;
border-radius: var(--mantine-radius-md);
background-color: var(--chip-background);
font-size: 10px;
font-size: var(--mantine-font-size-xxs);
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
@@ -2,7 +2,8 @@
width: 100%;
max-width: 100%;
min-width: 0;
overflow: clip;
overflow-x: clip;
overflow-y: visible;
}
.viewport[data-board-hydrated="false"] {
@@ -13,7 +13,7 @@
.board-grid-entry:has([data-board-widget-header]):hover,
.board-grid-entry:has([data-board-widget-header]):focus-within {
z-index: 10;
z-index: var(--homarr-z-index-widget-focus);
}
.board-grid-entry[data-grid-preview="true"] {