commit 643337eb6af242892777fb9f4b00da642f811b18
parent c7af77f8e33e47e8c8ebc26c752ba515ba8e6b75
Author: triesap <tyson@radroots.org>
Date: Tue, 11 Aug 2026 16:23:04 +0000
ui: implement the dashboard scaffold
- keep the top bar sidebar and main header fixed
- bound the main body without page-level scrolling
- place a full inspector beside the useful center width
- overlay the minimum inspector at compact desktop width
Diffstat:
2 files changed, 112 insertions(+), 0 deletions(-)
diff --git a/app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/DashboardScaffold.kt b/app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/DashboardScaffold.kt
@@ -0,0 +1,64 @@
+package org.harvestcircle.ui.shell
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun DashboardScaffold(
+ windowWidthDp: Int,
+ inspectorVisible: Boolean,
+ topBar: @Composable () -> Unit,
+ sidebar: @Composable () -> Unit,
+ mainHeader: @Composable () -> Unit,
+ mainBody: @Composable () -> Unit,
+ inspector: @Composable () -> Unit = {},
+) {
+ val placement = inspectorPlacement(windowWidthDp, inspectorVisible)
+ Column(Modifier.fillMaxSize().testTag("dashboard-scaffold")) {
+ Region("dashboard-top-bar", "Global top bar", Modifier.fillMaxWidth().height(56.dp), topBar)
+ Row(Modifier.fillMaxSize()) {
+ Region("dashboard-sidebar", "Workspace sidebar", Modifier.width(232.dp).fillMaxHeight(), sidebar)
+ Column(Modifier.weight(1f).fillMaxHeight()) {
+ Region("dashboard-main-header", "Main panel header", Modifier.fillMaxWidth().height(56.dp), mainHeader)
+ Region("dashboard-main-body", "Main panel body", Modifier.fillMaxSize(), mainBody)
+ }
+ if (placement == InspectorPlacement.Beside) {
+ Region("dashboard-inspector-beside", "Inspector", Modifier.width(400.dp).fillMaxHeight(), inspector)
+ }
+ }
+ }
+ if (placement == InspectorPlacement.Overlay) {
+ Box(Modifier.fillMaxSize(), contentAlignment = androidx.compose.ui.Alignment.CenterEnd) {
+ Region("dashboard-inspector-overlay", "Inspector", Modifier.width(360.dp).fillMaxHeight(), inspector)
+ }
+ }
+}
+
+@Composable
+private fun Region(
+ tag: String,
+ label: String,
+ modifier: Modifier,
+ content: @Composable () -> Unit,
+) {
+ Box(
+ modifier =
+ modifier
+ .semantics { contentDescription = label }
+ .testTag(tag),
+ ) {
+ content()
+ }
+}
diff --git a/app/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/DashboardScaffoldTest.kt b/app/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/DashboardScaffoldTest.kt
@@ -0,0 +1,48 @@
+package org.harvestcircle.ui.shell
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.v2.runComposeUiTest
+import androidx.compose.ui.unit.dp
+import kotlin.test.Test
+
+@OptIn(ExperimentalTestApi::class)
+class DashboardScaffoldTest {
+ @Test
+ fun preferredWindowKeepsFixedRegionsAndInspectorBeside() =
+ runComposeUiTest {
+ setContent { dashboard(width = 1280) }
+ onNodeWithTag("dashboard-top-bar").assertIsDisplayed()
+ onNodeWithTag("dashboard-sidebar").assertIsDisplayed()
+ onNodeWithTag("dashboard-main-header").assertIsDisplayed()
+ onNodeWithTag("dashboard-main-body").assertIsDisplayed()
+ onNodeWithTag("dashboard-inspector-beside").assertIsDisplayed()
+ }
+
+ @Test
+ fun minimumWindowMovesInspectorToOverlay() =
+ runComposeUiTest {
+ setContent { dashboard(width = 1100) }
+ onNodeWithTag("dashboard-inspector-overlay").assertIsDisplayed()
+ onNodeWithTag("dashboard-main-body").assertIsDisplayed()
+ }
+}
+
+@androidx.compose.runtime.Composable
+private fun dashboard(width: Int) {
+ Box(Modifier.requiredSize(width.dp, 720.dp)) {
+ DashboardScaffold(
+ windowWidthDp = width,
+ inspectorVisible = true,
+ topBar = {},
+ sidebar = {},
+ mainHeader = {},
+ mainBody = {},
+ inspector = {},
+ )
+ }
+}