This is an automated email from the ASF dual-hosted git repository.

malliaridis pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new c4e70b1d34f SOLR-17896: Introduce reusable navigation tabs (#3656)
c4e70b1d34f is described below

commit c4e70b1d34f675496c45fe46b40baffecf0c0449
Author: Christos Malliaridis <[email protected]>
AuthorDate: Mon Sep 22 16:58:14 2025 +0300

    SOLR-17896: Introduce reusable navigation tabs (#3656)
    
    Implements the navigation tabs component  in a reusable way and applies it 
to Configsets and Cluster for demonstration.
    
    The implementation does not apply styling from the designs, because the 
used composables from Material are not easily customized. The current 
implementation is also more accessible, as the expected designs used only a 
color indicator for the current selection, and the implementation uses an 
indicator line.
    
    This PR is based on #3521.
    
    ---------
    
    Co-authored-by: Sanjay Dutt <[email protected]>
---
 .../commonMain/composeResources/values/strings.xml |  13 ++
 .../ui/components/cluster/ClusterComponent.kt}     |  45 ++++---
 .../cluster/integration/DefaultClusterComponent.kt |  51 ++++++++
 .../components/configsets/ConfigsetsComponent.kt   |  61 +++++++++
 .../data/ListConfigsets.kt}                        |  19 ++-
 .../integration/DefaultConfigsetsComponent.kt      | 106 ++++++++++++++++
 .../integration/HttpConfigsetsStoreClient.kt       |  47 +++++++
 .../{main => configsets}/integration/Mappers.kt    |  20 +--
 .../overview/ConfigsetsOverviewComponent.kt}       |  13 +-
 .../DefaultConfigsetsOverviewComponent.kt}         |  21 ++--
 .../store/ConfigsetsStore.kt}                      |  26 ++--
 .../configsets/store/ConfigsetsStoreProvider.kt    | 108 ++++++++++++++++
 .../solr/ui/components/main/MainComponent.kt       |  10 +-
 .../main/integration/DefaultMainComponent.kt       |  28 ++++-
 .../solr/ui/components/main/integration/Mappers.kt |   3 +
 .../TabNavigationComponent.kt}                     |  22 ++--
 .../integration/DefaultTabNavigationComponent.kt   |  54 ++++++++
 .../integration/Mappers.kt => domain/Configset.kt} |  23 ++--
 .../apache/solr/ui/views/cluster/ClusterContent.kt |  51 ++++++++
 .../solr/ui/views/configsets/ConfigsetsContent.kt  |  96 ++++++++++++++
 .../solr/ui/views/configsets/ConfigsetsDropdown.kt | 108 ++++++++++++++++
 .../configsets/ConfigsetsOverviewContent.kt}       |  19 ++-
 .../org/apache/solr/ui/views/main/MainContent.kt   |  12 ++
 .../solr/ui/views/navigation/NavigationSideBar.kt  |   2 +
 .../solr/ui/views/navigation/NavigationTabs.kt     |  76 +++++++++++
 .../navigation/configsets/ConfigsetsTab.kt}        |  22 ++--
 .../org/apache/solr/ui/TestAppComponentContext.kt  |  61 +++++++++
 .../kotlin/org/apache/solr/ui/TestUtils.kt         |  20 +++
 .../DefaultConfigsetsComponentIntegrationTest.kt   | 135 ++++++++++++++++++++
 ...DefaultTabNavigationComponentIntegrationTest.kt | 139 +++++++++++++++++++++
 .../ui/views/configsets/ConfigsetsContentTest.kt   | 105 ++++++++++++++++
 .../views/configsets/ConfigsetsDropdownMenuTest.kt |  78 ++++++++++++
 .../preview/configsets/PreviewConfigsetsContent.kt |  75 +++++++++++
 33 files changed, 1552 insertions(+), 117 deletions(-)

diff --git a/solr/ui/src/commonMain/composeResources/values/strings.xml 
b/solr/ui/src/commonMain/composeResources/values/strings.xml
index 344a28ed3b8..6b4a6841c74 100644
--- a/solr/ui/src/commonMain/composeResources/values/strings.xml
+++ b/solr/ui/src/commonMain/composeResources/values/strings.xml
@@ -50,6 +50,13 @@
   <string name="nav_security">Security</string>
   <string name="nav_thread_dump">Thread Dump</string>
 
+  <!-- Configsets (nav) -->
+  <string name="configsets_update_configuration">Update Configuration</string>
+  <string name="configsets_index_query">Index / Query</string>
+  <string name="configsets_request_handlers">Request Handlers / 
Dispatchers</string>
+  <string name="configsets_search_components">Search Components</string>
+  <string name="no_configsets">No configsets available</string>
+
   <!-- Placeholders (ph) -->
   <string name="ph_solr_url">http://127.0.0.1:8983/</string>
 
@@ -60,13 +67,19 @@
   <!-- General Text -->
   <string name="authenticating">Authenticating...</string>
   <string name="community">Community</string>
+  <string name="cores">Cores</string>
   <string name="documentation">Documentation</string>
+  <string name="files">Files</string>
   <string name="irc">IRC</string>
   <string name="issue_tracker">Issue Tracker</string>
+  <string name="nodes">Nodes</string>
   <string name="slack">Slack</string>
   <string name="solr_query_syntax">Solr Query Syntax</string>
   <string name="support">Support</string>
   <string name="connecting">Connecting...</string>
+  <string name="overview">Overview</string>
+  <string name="schema">Schema</string>
+  <string name="zookeeper">Zookeeper</string>
 
   <!-- Input Labels -->
   <string name="label_username">Username</string>
diff --git a/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/TestUtils.kt 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/cluster/ClusterComponent.kt
similarity index 50%
copy from solr/ui/src/commonTest/kotlin/org/apache/solr/ui/TestUtils.kt
copy to 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/cluster/ClusterComponent.kt
index a157216b185..604a460bf24 100644
--- a/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/TestUtils.kt
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/cluster/ClusterComponent.kt
@@ -15,28 +15,33 @@
  * limitations under the License.
  */
 
-package org.apache.solr.ui
+package org.apache.solr.ui.components.cluster
 
-import io.ktor.client.engine.mock.MockEngine
-import io.ktor.client.engine.mock.MockEngineConfig
-import io.ktor.client.engine.mock.MockRequestHandler
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
+import kotlinx.serialization.Serializable
+import org.apache.solr.ui.components.cluster.ClusterComponent.Child
+import org.apache.solr.ui.components.cluster.ClusterComponent.ClusterTab
+import org.apache.solr.ui.components.navigation.TabNavigationComponent
 
 /**
- * Creates a mock engine with the given handlers and a standard test 
dispatcher.
- *
- * @param handlers The handlers to attach to the mock engine that will handle 
the requests
- * in the same order.
+ * Cluster component that represents our current Cluster section.
  *
+ * The cluster section's goal is to provide a "physical" representation of the 
connected Solr
+ * instance.
  */
-fun TestScope.createMockEngine(
-    vararg handlers: MockRequestHandler,
-    reuseHandlers: Boolean = true,
-): MockEngine = MockEngine(
-    MockEngineConfig().apply {
-        dispatcher = StandardTestDispatcher(testScheduler)
-        handlers.forEach(::addHandler)
-        this.reuseHandlers = reuseHandlers
-    },
-)
+interface ClusterComponent : TabNavigationComponent<ClusterTab, Child> {
+
+    sealed interface Child {
+        data object Zookeeper : Child
+
+        data object Nodes : Child
+
+        data object Cores : Child
+    }
+
+    @Serializable
+    enum class ClusterTab {
+        Zookeeper,
+        Nodes,
+        Cores,
+    }
+}
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/cluster/integration/DefaultClusterComponent.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/cluster/integration/DefaultClusterComponent.kt
new file mode 100644
index 00000000000..4058cafb560
--- /dev/null
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/cluster/integration/DefaultClusterComponent.kt
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.ui.components.cluster.integration
+
+import org.apache.solr.ui.components.cluster.ClusterComponent
+import org.apache.solr.ui.components.cluster.ClusterComponent.Child
+import org.apache.solr.ui.components.cluster.ClusterComponent.ClusterTab
+import org.apache.solr.ui.components.navigation.TabNavigationComponent
+import 
org.apache.solr.ui.components.navigation.integration.DefaultTabNavigationComponent
+import org.apache.solr.ui.utils.AppComponentContext
+
+class DefaultClusterComponent(
+    componentContext: AppComponentContext,
+    tabNavigation: TabNavigationComponent<ClusterTab, Child>,
+) : ClusterComponent,
+    AppComponentContext by componentContext,
+    TabNavigationComponent<ClusterTab, Child> by tabNavigation {
+
+    constructor(
+        componentContext: AppComponentContext,
+    ) : this(
+        componentContext = componentContext,
+        tabNavigation = DefaultTabNavigationComponent<ClusterTab, Child>(
+            componentContext = componentContext,
+            initialTab = ClusterTab.Zookeeper,
+            tabSerializer = ClusterTab.serializer(),
+            childFactory = { configuration, childContext ->
+                when (configuration.tab) {
+                    ClusterTab.Zookeeper -> Child.Zookeeper
+                    ClusterTab.Nodes -> Child.Nodes
+                    ClusterTab.Cores -> Child.Cores
+                }
+            },
+        ),
+    )
+}
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/ConfigsetsComponent.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/ConfigsetsComponent.kt
new file mode 100644
index 00000000000..19580da5567
--- /dev/null
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/ConfigsetsComponent.kt
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.ui.components.configsets
+
+import kotlinx.coroutines.flow.StateFlow
+import org.apache.solr.ui.components.configsets.ConfigsetsComponent.Child
+import 
org.apache.solr.ui.components.configsets.overview.ConfigsetsOverviewComponent
+import org.apache.solr.ui.components.navigation.TabNavigationComponent
+import org.apache.solr.ui.domain.Configset
+import org.apache.solr.ui.views.navigation.configsets.ConfigsetsTab
+
+/**
+ * The configsets component provides the main entry point for managing Solr's 
configets.
+ */
+interface ConfigsetsComponent : TabNavigationComponent<ConfigsetsTab, Child> {
+
+    /**
+     * All possible navigation targets (children) within the Configsets 
section.
+     */
+    sealed interface Child {
+        data class Overview(val component: ConfigsetsOverviewComponent) : Child
+
+        /**
+         * TODO Remove once other sections are added
+         */
+        data class Placeholder(val tabName: String) : Child
+    }
+
+    /**
+     * Model that holds the data of the [ConfigsetsComponent].
+     *
+     * @property configsets The configsets names available.
+     * @property selectedConfigset The current configset name to display. 
Leave empty if no
+     * selection is made.
+     */
+    data class Model(
+        val configsets: List<Configset> = emptyList(),
+        val selectedConfigset: String = "",
+    )
+
+    /** Hot, observable stream of [Model] for Compose/UI. */
+    val model: StateFlow<Model>
+
+    /** Select the active configset by name. */
+    fun onSelectConfigset(name: String)
+}
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/data/ListConfigsets.kt
similarity index 64%
copy from 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
copy to 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/data/ListConfigsets.kt
index 19557f36d01..738c6d349bf 100644
--- 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/data/ListConfigsets.kt
@@ -14,15 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.solr.ui.components.configsets.data
 
-package org.apache.solr.ui.components.main.integration
+import kotlinx.serialization.Serializable
 
-import org.apache.solr.ui.components.main.MainComponent
-import org.apache.solr.ui.views.navigation.MainMenu
-
-val MainComponent.Child.asMainMenu: MainMenu
-    get() = when (this) {
-        // TODO Add additional mappings once more children are supported
-        is MainComponent.Child.Environment -> MainMenu.Environment
-        is MainComponent.Child.Logging -> MainMenu.Logging
-    }
+/**
+ * Configsets API model that holds a list of configsets.
+ */
+@Serializable
+data class ListConfigsets(
+    val configSets: List<String> = emptyList(),
+)
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/integration/DefaultConfigsetsComponent.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/integration/DefaultConfigsetsComponent.kt
new file mode 100644
index 00000000000..05d399f93d1
--- /dev/null
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/integration/DefaultConfigsetsComponent.kt
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.ui.components.configsets.integration
+
+import com.arkivanov.decompose.childContext
+import com.arkivanov.mvikotlin.core.instancekeeper.getStore
+import com.arkivanov.mvikotlin.core.store.StoreFactory
+import com.arkivanov.mvikotlin.extensions.coroutines.stateFlow
+import io.ktor.client.HttpClient
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import org.apache.solr.ui.components.configsets.ConfigsetsComponent
+import org.apache.solr.ui.components.configsets.ConfigsetsComponent.Child
+import 
org.apache.solr.ui.components.configsets.ConfigsetsComponent.Child.Overview
+import 
org.apache.solr.ui.components.configsets.ConfigsetsComponent.Child.Placeholder
+import 
org.apache.solr.ui.components.configsets.overview.integration.DefaultConfigsetsOverviewComponent
+import org.apache.solr.ui.components.configsets.store.ConfigsetsStore.Intent
+import org.apache.solr.ui.components.configsets.store.ConfigsetsStoreProvider
+import org.apache.solr.ui.components.navigation.TabNavigationComponent
+import 
org.apache.solr.ui.components.navigation.TabNavigationComponent.Configuration
+import 
org.apache.solr.ui.components.navigation.integration.DefaultTabNavigationComponent
+import org.apache.solr.ui.utils.AppComponentContext
+import org.apache.solr.ui.utils.coroutineScope
+import org.apache.solr.ui.utils.map
+import org.apache.solr.ui.views.navigation.configsets.ConfigsetsTab
+
+/**
+ * Default implementation of the [ConfigsetsComponent].
+ */
+class DefaultConfigsetsComponent internal constructor(
+    componentContext: AppComponentContext,
+    tabNavigation: TabNavigationComponent<ConfigsetsTab, Child>,
+    storeFactory: StoreFactory,
+    httpClient: HttpClient,
+) : ConfigsetsComponent,
+    AppComponentContext by componentContext,
+    TabNavigationComponent<ConfigsetsTab, Child> by tabNavigation {
+
+    constructor(
+        componentContext: AppComponentContext,
+        storeFactory: StoreFactory,
+        httpClient: HttpClient,
+    ) : this (
+        componentContext = componentContext,
+        storeFactory = storeFactory,
+        httpClient = httpClient,
+        tabNavigation = DefaultTabNavigationComponent<ConfigsetsTab, Child>(
+            componentContext = componentContext.childContext("ConfigsetsTabs"),
+            initialTab = ConfigsetsTab.Overview,
+            tabSerializer = ConfigsetsTab.serializer(),
+            childFactory = { configuration, childContext ->
+                configsetsChildFactory(storeFactory, httpClient, 
configuration, childContext)
+            },
+        ),
+    )
+
+    private val mainScope = coroutineScope(SupervisorJob() + mainContext)
+    private val ioScope = coroutineScope(SupervisorJob() + ioContext)
+
+    private val store = instanceKeeper.getStore {
+        ConfigsetsStoreProvider(
+            storeFactory = storeFactory,
+            client = HttpConfigsetsStoreClient(httpClient),
+            mainContext = mainScope.coroutineContext,
+            ioContext = ioScope.coroutineContext,
+        ).provide()
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    override val model = store.stateFlow.map(mainScope, configsetsStateToModel)
+
+    override fun onSelectConfigset(name: String) {
+        store.accept(Intent.SelectConfigSet(configSetName = name))
+    }
+}
+
+fun configsetsChildFactory(
+    storeFactory: StoreFactory,
+    httpClient: HttpClient,
+    configuration: Configuration<ConfigsetsTab>,
+    childContext: AppComponentContext,
+): Child = when (configuration.tab) {
+    ConfigsetsTab.Overview -> Overview(
+        DefaultConfigsetsOverviewComponent(
+            componentContext = childContext,
+            storeFactory = storeFactory,
+            httpClient = httpClient,
+        ),
+    )
+    else -> Placeholder(tabName = configuration.tab.name)
+}
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/integration/HttpConfigsetsStoreClient.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/integration/HttpConfigsetsStoreClient.kt
new file mode 100644
index 00000000000..cd8a7eaa726
--- /dev/null
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/integration/HttpConfigsetsStoreClient.kt
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.ui.components.configsets.integration
+
+import io.ktor.client.HttpClient
+import io.ktor.client.call.body
+import io.ktor.client.request.get
+import io.ktor.http.isSuccess
+import org.apache.solr.ui.components.configsets.data.ListConfigsets
+import org.apache.solr.ui.components.configsets.store.ConfigsetsStoreProvider
+
+/**
+ * Client implementation of the [ConfigsetsStoreProvider.Client] that makes use
+ * of a preconfigured HTTP client for accessing the Solr API.
+ *
+ * @property httpClient HTTP client to use for accessing the API. The client 
has to be
+ * configured with a default request that includes the host, port and schema. 
The client
+ * should also include the necessary authentication data if authentication / 
authorization
+ * is enabled.
+ */
+class HttpConfigsetsStoreClient(
+    private val httpClient: HttpClient,
+) : ConfigsetsStoreProvider.Client {
+    override suspend fun fetchConfigSets(): Result<ListConfigsets> {
+        val response = httpClient.get("api/configsets")
+        return when {
+            response.status.isSuccess() -> Result.success(response.body())
+            else -> Result.failure(Exception("Unknown Error"))
+            // TODO Add proper error handling
+        }
+    }
+}
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/integration/Mappers.kt
similarity index 60%
copy from 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
copy to 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/integration/Mappers.kt
index 19557f36d01..00a428faa32 100644
--- 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/integration/Mappers.kt
@@ -15,14 +15,16 @@
  * limitations under the License.
  */
 
-package org.apache.solr.ui.components.main.integration
+package org.apache.solr.ui.components.configsets.integration
 
-import org.apache.solr.ui.components.main.MainComponent
-import org.apache.solr.ui.views.navigation.MainMenu
+import org.apache.solr.ui.components.configsets.ConfigsetsComponent
+import org.apache.solr.ui.components.configsets.store.ConfigsetsStore
+import org.apache.solr.ui.domain.Configset
 
-val MainComponent.Child.asMainMenu: MainMenu
-    get() = when (this) {
-        // TODO Add additional mappings once more children are supported
-        is MainComponent.Child.Environment -> MainMenu.Environment
-        is MainComponent.Child.Logging -> MainMenu.Logging
-    }
+internal val configsetsStateToModel: (ConfigsetsStore.State) -> 
ConfigsetsComponent.Model = {
+    ConfigsetsComponent.Model(
+        configsets = it.configSets.configSets.sorted()
+            .map { s -> Configset(s) },
+        selectedConfigset = it.selectedConfigset ?: "",
+    )
+}
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/overview/ConfigsetsOverviewComponent.kt
similarity index 64%
copy from 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
copy to 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/overview/ConfigsetsOverviewComponent.kt
index 19557f36d01..eed272cb259 100644
--- 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/overview/ConfigsetsOverviewComponent.kt
@@ -14,15 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.solr.ui.components.configsets.overview
 
-package org.apache.solr.ui.components.main.integration
-
-import org.apache.solr.ui.components.main.MainComponent
-import org.apache.solr.ui.views.navigation.MainMenu
-
-val MainComponent.Child.asMainMenu: MainMenu
-    get() = when (this) {
-        // TODO Add additional mappings once more children are supported
-        is MainComponent.Child.Environment -> MainMenu.Environment
-        is MainComponent.Child.Logging -> MainMenu.Logging
-    }
+interface ConfigsetsOverviewComponent
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/overview/integration/DefaultConfigsetsOverviewComponent.kt
similarity index 60%
copy from 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
copy to 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/overview/integration/DefaultConfigsetsOverviewComponent.kt
index 19557f36d01..240b394815b 100644
--- 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/overview/integration/DefaultConfigsetsOverviewComponent.kt
@@ -14,15 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.solr.ui.components.configsets.overview.integration
 
-package org.apache.solr.ui.components.main.integration
+import com.arkivanov.mvikotlin.core.store.StoreFactory
+import io.ktor.client.HttpClient
+import 
org.apache.solr.ui.components.configsets.overview.ConfigsetsOverviewComponent
+import org.apache.solr.ui.utils.AppComponentContext
 
-import org.apache.solr.ui.components.main.MainComponent
-import org.apache.solr.ui.views.navigation.MainMenu
-
-val MainComponent.Child.asMainMenu: MainMenu
-    get() = when (this) {
-        // TODO Add additional mappings once more children are supported
-        is MainComponent.Child.Environment -> MainMenu.Environment
-        is MainComponent.Child.Logging -> MainMenu.Logging
-    }
+class DefaultConfigsetsOverviewComponent(
+    componentContext: AppComponentContext,
+    storeFactory: StoreFactory,
+    httpClient: HttpClient,
+) : ConfigsetsOverviewComponent,
+    AppComponentContext by componentContext
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/store/ConfigsetsStore.kt
similarity index 51%
copy from 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
copy to 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/store/ConfigsetsStore.kt
index 19557f36d01..7dbbb9f69dd 100644
--- 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/store/ConfigsetsStore.kt
@@ -14,15 +14,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.solr.ui.components.configsets.store
 
-package org.apache.solr.ui.components.main.integration
+import com.arkivanov.mvikotlin.core.store.Store
+import org.apache.solr.ui.components.configsets.data.ListConfigsets
+import org.apache.solr.ui.components.configsets.store.ConfigsetsStore.Intent
+import org.apache.solr.ui.components.configsets.store.ConfigsetsStore.State
+import org.apache.solr.ui.views.navigation.configsets.ConfigsetsTab
 
-import org.apache.solr.ui.components.main.MainComponent
-import org.apache.solr.ui.views.navigation.MainMenu
+internal interface ConfigsetsStore : Store<Intent, State, Nothing> {
 
-val MainComponent.Child.asMainMenu: MainMenu
-    get() = when (this) {
-        // TODO Add additional mappings once more children are supported
-        is MainComponent.Child.Environment -> MainMenu.Environment
-        is MainComponent.Child.Logging -> MainMenu.Logging
+    sealed interface Intent {
+        /**
+         * Intent for selecting configset.
+         */
+        data class SelectConfigSet(val configSetName: String) : Intent
     }
+
+    data class State(
+        val selectedConfigset: String? = null,
+        val configSets: ListConfigsets = ListConfigsets(),
+    )
+}
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/store/ConfigsetsStoreProvider.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/store/ConfigsetsStoreProvider.kt
new file mode 100644
index 00000000000..e3c73e4bcfd
--- /dev/null
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/configsets/store/ConfigsetsStoreProvider.kt
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.ui.components.configsets.store
+
+import com.arkivanov.mvikotlin.core.store.Reducer
+import com.arkivanov.mvikotlin.core.store.SimpleBootstrapper
+import com.arkivanov.mvikotlin.core.store.Store
+import com.arkivanov.mvikotlin.core.store.StoreFactory
+import com.arkivanov.mvikotlin.extensions.coroutines.CoroutineExecutor
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.apache.solr.ui.components.configsets.data.ListConfigsets
+import org.apache.solr.ui.components.configsets.store.ConfigsetsStore.Intent
+import org.apache.solr.ui.components.configsets.store.ConfigsetsStore.State
+import org.apache.solr.ui.views.navigation.configsets.ConfigsetsTab
+
+/**
+ * Store provider that [provide]s instances of [ConfigsetsStore].
+ *
+ * @property storeFactory Store factory to use for creating the store.
+ * @property client Client implementation to use for resolving [Intent]s and 
[Action]s.
+ * @property ioContext Coroutine context used for IO activity.
+ */
+internal class ConfigsetsStoreProvider(
+    private val storeFactory: StoreFactory,
+    private val client: Client,
+    private val mainContext: CoroutineContext,
+    private val ioContext: CoroutineContext,
+) {
+
+    fun provide(): ConfigsetsStore = object :
+        ConfigsetsStore,
+        Store<Intent, State, Nothing> by storeFactory.create(
+            name = "ConfigsetsStore",
+            initialState = State(),
+            bootstrapper = SimpleBootstrapper(Action.FetchInitialConfigsets),
+            executorFactory = ::ExecutorImpl,
+            reducer = ReducerImpl,
+        ) {}
+
+    private sealed interface Action {
+        /**
+         * Action used for initiating the initial fetch of configsets data.
+         */
+        data object FetchInitialConfigsets : Action
+    }
+
+    private sealed interface Message {
+        data class ConfigSetsUpdated(val configsets: ListConfigsets) : Message
+        data class SelectedConfigSetChanged(val configsetName: String) : 
Message
+    }
+
+    private inner class ExecutorImpl : CoroutineExecutor<Intent, Action, 
State, Message, Nothing>(mainContext) {
+        override fun executeAction(action: Action) = when (action) {
+            Action.FetchInitialConfigsets -> {
+                fetchConfigSets()
+            }
+        }
+
+        override fun executeIntent(intent: Intent) = when (intent) {
+            is Intent.SelectConfigSet -> 
dispatch(Message.SelectedConfigSetChanged(intent.configSetName))
+        }
+
+        private fun fetchConfigSets() {
+            scope.launch {
+                withContext(ioContext) {
+                    client.fetchConfigSets()
+                }.onSuccess { sets ->
+                    dispatch(Message.ConfigSetsUpdated(sets))
+                }
+            }
+        }
+    }
+
+    /**
+     * Reducer implementation that consumes [Message]s and updates the store's 
[State].
+     */
+    private object ReducerImpl : Reducer<State, Message> {
+        override fun State.reduce(msg: Message): State = when (msg) {
+            is Message.ConfigSetsUpdated -> copy(configSets = msg.configsets)
+            is Message.SelectedConfigSetChanged -> copy(selectedConfigset = 
msg.configsetName)
+        }
+    }
+
+    /**
+     * Client interface for fetching configsets information.
+     */
+    interface Client {
+        /** To fetch a list of configsets. */
+        suspend fun fetchConfigSets(): Result<ListConfigsets>
+    }
+}
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/MainComponent.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/MainComponent.kt
index 33516e85e66..56e1ad29284 100644
--- 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/MainComponent.kt
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/MainComponent.kt
@@ -19,10 +19,14 @@ package org.apache.solr.ui.components.main
 
 import com.arkivanov.decompose.router.stack.ChildStack
 import com.arkivanov.decompose.value.Value
+import org.apache.solr.ui.components.cluster.ClusterComponent
+import org.apache.solr.ui.components.configsets.ConfigsetsComponent
 import org.apache.solr.ui.components.environment.EnvironmentComponent
 import org.apache.solr.ui.components.logging.LoggingComponent
 import org.apache.solr.ui.components.navigation.NavigationComponent
+import org.apache.solr.ui.components.navigation.TabNavigationComponent
 import org.apache.solr.ui.views.navigation.MainMenu
+import org.apache.solr.ui.views.navigation.configsets.ConfigsetsTab
 
 /**
  * Main component of the application that is used as base for users with 
access.
@@ -60,14 +64,12 @@ interface MainComponent : NavigationComponent {
         // TODO Uncomment once MetricsComponent available
         // data class Metrics(val component: MetricsComponent): Child
 
-        // TODO Uncomment once ClusterComponent available
-        // data class Cluster(val component: ClusterComponent): Child
+        data class Cluster(val component: ClusterComponent) : Child
 
         // TODO Uncomment once SecurityComponent available
         // data class Security(val component: SecurityComponent): Child
 
-        // TODO Uncomment once ConfigsetsComponent available
-        // data class Configsets(val component: ConfigsetsComponent): Child
+        data class Configsets(val component: ConfigsetsComponent) : Child
 
         // TODO Uncomment once MetricsComponent available
         // data class Collections(val component: CollectionsComponent): Child
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/DefaultMainComponent.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/DefaultMainComponent.kt
index eb0dd42c549..4de94a395e5 100644
--- 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/DefaultMainComponent.kt
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/DefaultMainComponent.kt
@@ -25,6 +25,10 @@ import com.arkivanov.decompose.value.Value
 import com.arkivanov.mvikotlin.core.store.StoreFactory
 import io.ktor.client.HttpClient
 import kotlinx.serialization.Serializable
+import org.apache.solr.ui.components.cluster.ClusterComponent
+import 
org.apache.solr.ui.components.cluster.integration.DefaultClusterComponent
+import org.apache.solr.ui.components.configsets.ConfigsetsComponent
+import 
org.apache.solr.ui.components.configsets.integration.DefaultConfigsetsComponent
 import org.apache.solr.ui.components.environment.EnvironmentComponent
 import 
org.apache.solr.ui.components.environment.integration.DefaultEnvironmentComponent
 import org.apache.solr.ui.components.logging.LoggingComponent
@@ -39,6 +43,8 @@ class DefaultMainComponent internal constructor(
     componentContext: AppComponentContext,
     storeFactory: StoreFactory,
     destination: String? = null,
+    private val clusterComponent: (AppComponentContext) -> ClusterComponent,
+    private val configsetsComponent: (AppComponentContext) -> 
ConfigsetsComponent,
     private val environmentComponent: (AppComponentContext) -> 
EnvironmentComponent,
     private val loggingComponent: (AppComponentContext) -> LoggingComponent,
     private val output: (Output) -> Unit,
@@ -66,6 +72,18 @@ class DefaultMainComponent internal constructor(
         storeFactory = storeFactory,
         destination = destination,
         output = output,
+        clusterComponent = { childContext ->
+            DefaultClusterComponent(
+                componentContext = childContext,
+            )
+        },
+        configsetsComponent = { childContext ->
+            DefaultConfigsetsComponent(
+                componentContext = childContext,
+                storeFactory = storeFactory,
+                httpClient = httpClient,
+            )
+        },
         environmentComponent = { childContext ->
             DefaultEnvironmentComponent(
                 componentContext = childContext,
@@ -94,6 +112,8 @@ class DefaultMainComponent internal constructor(
      */
     private fun calculateInitialStack(destination: String?): 
List<Configuration> = listOf(
         when (destination) {
+            "cluster" -> Configuration.Cluster
+            "configsets" -> Configuration.Configsets
             "environment" -> Configuration.Environment
             "logging" -> Configuration.Logging
             else -> Configuration.Environment
@@ -112,17 +132,13 @@ class DefaultMainComponent internal constructor(
         // Configuration.Metrics ->
         //     
NavigationComponent.Child.Metrics(metricsComponent(componentContext))
 
-        // TODO Uncomment once Cluster available
-        // Configuration.Cluster ->
-        //     
NavigationComponent.Child.Cluster(clusterComponent(componentContext))
+        Configuration.Cluster -> 
Child.Cluster(clusterComponent(componentContext))
 
         // TODO Uncomment once Security available
         // Configuration.Security ->
         //     
NavigationComponent.Child.Security(securityComponent(componentContext))
 
-        // TODO Uncomment once Configsets available
-        // Configuration.Configsets ->
-        //     
NavigationComponent.Child.Configsets(configsetsComponent(componentContext))
+        Configuration.Configsets -> 
Child.Configsets(configsetsComponent(componentContext))
 
         // TODO Uncomment once Collections available
         // Configuration.Collections ->
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
index 19557f36d01..66b1c9e2abf 100644
--- 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
@@ -18,11 +18,14 @@
 package org.apache.solr.ui.components.main.integration
 
 import org.apache.solr.ui.components.main.MainComponent
+import org.apache.solr.ui.components.root.RootComponent
 import org.apache.solr.ui.views.navigation.MainMenu
 
 val MainComponent.Child.asMainMenu: MainMenu
     get() = when (this) {
         // TODO Add additional mappings once more children are supported
+        is MainComponent.Child.Cluster -> MainMenu.Cluster
+        is MainComponent.Child.Configsets -> MainMenu.Configsets
         is MainComponent.Child.Environment -> MainMenu.Environment
         is MainComponent.Child.Logging -> MainMenu.Logging
     }
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/navigation/TabNavigationComponent.kt
similarity index 64%
copy from 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
copy to 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/navigation/TabNavigationComponent.kt
index 19557f36d01..9b07b5295eb 100644
--- 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/navigation/TabNavigationComponent.kt
@@ -15,14 +15,18 @@
  * limitations under the License.
  */
 
-package org.apache.solr.ui.components.main.integration
+package org.apache.solr.ui.components.navigation
 
-import org.apache.solr.ui.components.main.MainComponent
-import org.apache.solr.ui.views.navigation.MainMenu
+import com.arkivanov.decompose.router.slot.ChildSlot
+import com.arkivanov.decompose.value.Value
+import kotlinx.serialization.Serializable
 
-val MainComponent.Child.asMainMenu: MainMenu
-    get() = when (this) {
-        // TODO Add additional mappings once more children are supported
-        is MainComponent.Child.Environment -> MainMenu.Environment
-        is MainComponent.Child.Logging -> MainMenu.Logging
-    }
+interface TabNavigationComponent<T : Enum<T>, C : Any> {
+
+    val tabSlot: Value<ChildSlot<Configuration<T>, C>>
+
+    fun onNavigate(tab: T)
+
+    @Serializable
+    data class Configuration<T : Enum<T>>(val tab: T)
+}
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/navigation/integration/DefaultTabNavigationComponent.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/navigation/integration/DefaultTabNavigationComponent.kt
new file mode 100644
index 00000000000..e7fffe2076e
--- /dev/null
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/navigation/integration/DefaultTabNavigationComponent.kt
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.ui.components.navigation.integration
+
+import com.arkivanov.decompose.router.slot.ChildSlot
+import com.arkivanov.decompose.router.slot.SlotNavigation
+import com.arkivanov.decompose.router.slot.activate
+import com.arkivanov.decompose.router.slot.childSlot
+import com.arkivanov.decompose.value.Value
+import com.arkivanov.essenty.lifecycle.doOnCreate
+import com.arkivanov.essenty.lifecycle.doOnResume
+import kotlinx.serialization.KSerializer
+import org.apache.solr.ui.components.navigation.TabNavigationComponent
+import 
org.apache.solr.ui.components.navigation.TabNavigationComponent.Configuration
+import org.apache.solr.ui.utils.AppComponentContext
+
+class DefaultTabNavigationComponent<T : Enum<T>, C : Any>(
+    componentContext: AppComponentContext,
+    initialTab: T,
+    tabSerializer: KSerializer<T>,
+    childFactory: (Configuration<T>, AppComponentContext) -> C,
+) : TabNavigationComponent<T, C>,
+    AppComponentContext by componentContext {
+
+    private val navigation = SlotNavigation<Configuration<T>>()
+
+    override val tabSlot: Value<ChildSlot<Configuration<T>, C>> = childSlot(
+        source = navigation,
+        serializer = Configuration.serializer(tabSerializer),
+        handleBackButton = true,
+        childFactory = childFactory,
+    )
+
+    init {
+        navigation.activate(configuration = Configuration(initialTab))
+    }
+
+    override fun onNavigate(tab: T) = navigation.activate(configuration = 
Configuration(tab))
+}
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
 b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/domain/Configset.kt
similarity index 64%
copy from 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
copy to solr/ui/src/commonMain/kotlin/org/apache/solr/ui/domain/Configset.kt
index 19557f36d01..921f3ca9c16 100644
--- 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
+++ b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/domain/Configset.kt
@@ -15,14 +15,17 @@
  * limitations under the License.
  */
 
-package org.apache.solr.ui.components.main.integration
+package org.apache.solr.ui.domain
 
-import org.apache.solr.ui.components.main.MainComponent
-import org.apache.solr.ui.views.navigation.MainMenu
-
-val MainComponent.Child.asMainMenu: MainMenu
-    get() = when (this) {
-        // TODO Add additional mappings once more children are supported
-        is MainComponent.Child.Environment -> MainMenu.Environment
-        is MainComponent.Child.Logging -> MainMenu.Logging
-    }
+/**
+ * Configset entity that represents a basic configset. This data class does 
only hold the basic
+ * information of a configset.
+ *
+ * Note that the configsets are uniquely identified by the name right now and 
therefore do not
+ * use a separate ID property.
+ *
+ * @property name The name and unique identifier of the configset.
+ */
+data class Configset(
+    val name: String = "",
+)
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/cluster/ClusterContent.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/cluster/ClusterContent.kt
new file mode 100644
index 00000000000..bc875b427b0
--- /dev/null
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/cluster/ClusterContent.kt
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.ui.views.cluster
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import org.apache.solr.ui.components.cluster.ClusterComponent
+import org.apache.solr.ui.components.cluster.ClusterComponent.ClusterTab
+import org.apache.solr.ui.generated.resources.Res
+import org.apache.solr.ui.generated.resources.cores
+import org.apache.solr.ui.generated.resources.nodes
+import org.apache.solr.ui.generated.resources.zookeeper
+import org.apache.solr.ui.views.navigation.NavigationTabs
+import org.jetbrains.compose.resources.StringResource
+
+@Composable
+fun ClusterContent(
+    component: ClusterComponent,
+    modifier: Modifier = Modifier,
+) = Column(modifier = modifier) {
+    NavigationTabs(
+        component = component,
+        entries = ClusterTab.entries,
+        mapper = ::clusterTabsMapper,
+        modifier = Modifier.padding(1.dp),
+    )
+}
+
+private fun clusterTabsMapper(tab: ClusterTab): StringResource = when (tab) {
+    ClusterTab.Zookeeper -> Res.string.zookeeper
+    ClusterTab.Nodes -> Res.string.nodes
+    ClusterTab.Cores -> Res.string.cores
+}
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/configsets/ConfigsetsContent.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/configsets/ConfigsetsContent.kt
new file mode 100644
index 00000000000..24faf766cc7
--- /dev/null
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/configsets/ConfigsetsContent.kt
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.ui.views.configsets
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.arkivanov.decompose.extensions.compose.subscribeAsState
+import org.apache.solr.ui.components.configsets.ConfigsetsComponent
+import org.apache.solr.ui.components.configsets.ConfigsetsComponent.Child
+import org.apache.solr.ui.generated.resources.Res
+import org.apache.solr.ui.generated.resources.configsets_index_query
+import org.apache.solr.ui.generated.resources.configsets_request_handlers
+import org.apache.solr.ui.generated.resources.configsets_search_components
+import org.apache.solr.ui.generated.resources.configsets_update_configuration
+import org.apache.solr.ui.generated.resources.files
+import org.apache.solr.ui.generated.resources.overview
+import org.apache.solr.ui.generated.resources.schema
+import org.apache.solr.ui.views.navigation.NavigationTabs
+import org.apache.solr.ui.views.navigation.configsets.ConfigsetsTab
+
+@OptIn(ExperimentalLayoutApi::class)
+@Composable
+fun ConfigsetsContent(
+    component: ConfigsetsComponent,
+    modifier: Modifier = Modifier,
+) = FlowRow(
+    modifier = modifier,
+    horizontalArrangement = Arrangement.spacedBy(16.dp),
+    verticalArrangement = Arrangement.spacedBy(16.dp),
+) {
+    val model by component.model.collectAsState()
+    val slot by component.tabSlot.subscribeAsState()
+    val currentChild = slot.child
+
+    Column(Modifier.fillMaxSize()) {
+        NavigationTabs(
+            component = component,
+            entries = ConfigsetsTab.entries,
+            mapper = ::tabLabelRes,
+            modifier = Modifier.padding(1.dp),
+        )
+        ConfigsetsDropdown(
+            selectedConfigSet = model.selectedConfigset,
+            selectConfigset = component::onSelectConfigset,
+            availableConfigsets = model.configsets,
+        )
+
+        Box(
+            Modifier
+                .fillMaxSize()
+                .padding(16.dp),
+        ) {
+            currentChild?.let {
+                when (val child = it.instance) {
+                    is Child.Overview -> ConfigsetsOverviewContent(component = 
child.component)
+                    is Child.Placeholder -> Text(text = child.tabName)
+                }
+            }
+        }
+    }
+}
+
+private fun tabLabelRes(item: ConfigsetsTab) = when (item) {
+    ConfigsetsTab.Overview -> Res.string.overview
+    ConfigsetsTab.Files -> Res.string.files
+    ConfigsetsTab.Schema -> Res.string.schema
+    ConfigsetsTab.UpdateConfig -> Res.string.configsets_update_configuration
+    ConfigsetsTab.IndexQuery -> Res.string.configsets_index_query
+    ConfigsetsTab.Handlers -> Res.string.configsets_request_handlers
+    ConfigsetsTab.SearchComponents -> Res.string.configsets_search_components
+}
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/configsets/ConfigsetsDropdown.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/configsets/ConfigsetsDropdown.kt
new file mode 100644
index 00000000000..ae37f14d716
--- /dev/null
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/configsets/ConfigsetsDropdown.kt
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.ui.views.configsets
+
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuBox
+import androidx.compose.material3.ExposedDropdownMenuDefaults
+import androidx.compose.material3.MenuAnchorType
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.unit.dp
+import org.apache.solr.ui.domain.Configset
+import org.apache.solr.ui.generated.resources.Res
+import org.apache.solr.ui.generated.resources.nav_configsets
+import org.apache.solr.ui.generated.resources.no_configsets
+import org.jetbrains.compose.resources.stringResource
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ConfigsetsDropdown(
+    selectedConfigSet: String,
+    selectConfigset: (String) -> Unit,
+    availableConfigsets: List<Configset>,
+    modifier: Modifier = Modifier,
+) {
+    var expanded by remember { mutableStateOf(false) }
+    val enabled = availableConfigsets.isNotEmpty()
+
+    Row(
+        modifier = modifier
+            .fillMaxWidth()
+            .padding(horizontal = 16.dp, vertical = 8.dp),
+        verticalAlignment = Alignment.CenterVertically,
+    ) {
+        ExposedDropdownMenuBox(
+            expanded = expanded,
+            onExpandedChange = { expanded = it },
+            modifier = Modifier.widthIn(min = 256.dp).weight(1f),
+        ) {
+            OutlinedTextField(
+                value = selectedConfigSet,
+                onValueChange = {},
+                readOnly = true,
+                enabled = enabled,
+                label = { Text(stringResource(Res.string.nav_configsets)) },
+                placeholder = {
+                    if (availableConfigsets.isEmpty()) {
+                        Text(
+                            modifier = 
Modifier.testTag("no_configsets_placeholder"),
+                            text = stringResource(Res.string.no_configsets),
+                        )
+                    }
+                },
+                trailingIcon = { 
ExposedDropdownMenuDefaults.TrailingIcon(expanded) },
+                modifier = Modifier
+                    .menuAnchor(
+                        type = MenuAnchorType.PrimaryNotEditable,
+                        enabled = enabled,
+                    )
+                    .fillMaxWidth()
+                    .testTag("configsets_dropdown"),
+            )
+            ExposedDropdownMenu(
+                modifier = 
Modifier.testTag("configsets_exposed_dropdown_menu"),
+                expanded = expanded,
+                onDismissRequest = { expanded = false },
+            ) {
+                availableConfigsets.forEach { configset ->
+                    DropdownMenuItem(
+                        modifier = Modifier.testTag(tag = configset.name),
+                        text = { Text(configset.name) },
+                        onClick = {
+                            selectConfigset(configset.name)
+                            expanded = false
+                        },
+                    )
+                }
+            }
+        }
+    }
+}
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/configsets/ConfigsetsOverviewContent.kt
similarity index 64%
copy from 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
copy to 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/configsets/ConfigsetsOverviewContent.kt
index 19557f36d01..623beda8bda 100644
--- 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/configsets/ConfigsetsOverviewContent.kt
@@ -14,15 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.solr.ui.views.configsets
 
-package org.apache.solr.ui.components.main.integration
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import 
org.apache.solr.ui.components.configsets.overview.ConfigsetsOverviewComponent
 
-import org.apache.solr.ui.components.main.MainComponent
-import org.apache.solr.ui.views.navigation.MainMenu
-
-val MainComponent.Child.asMainMenu: MainMenu
-    get() = when (this) {
-        // TODO Add additional mappings once more children are supported
-        is MainComponent.Child.Environment -> MainMenu.Environment
-        is MainComponent.Child.Logging -> MainMenu.Logging
-    }
+@Composable
+fun ConfigsetsOverviewContent(component: ConfigsetsOverviewComponent, 
modifier: Modifier = Modifier) {
+    Text("Overview section")
+}
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/main/MainContent.kt 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/main/MainContent.kt
index a905700d2b6..0bc2031c0e8 100644
--- a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/main/MainContent.kt
+++ b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/main/MainContent.kt
@@ -32,6 +32,8 @@ import 
com.arkivanov.decompose.extensions.compose.stack.Children
 import com.arkivanov.decompose.extensions.compose.subscribeAsState
 import org.apache.solr.ui.components.main.MainComponent
 import org.apache.solr.ui.components.main.integration.asMainMenu
+import org.apache.solr.ui.views.cluster.ClusterContent
+import org.apache.solr.ui.views.configsets.ConfigsetsContent
 import org.apache.solr.ui.views.environment.EnvironmentContent
 import org.apache.solr.ui.views.logging.LoggingContent
 import org.apache.solr.ui.views.navigation.NavigationSideBar
@@ -63,6 +65,16 @@ fun MainContent(
             modifier = Modifier.weight(1f),
         ) {
             when (val child = it.instance) {
+                is MainComponent.Child.Cluster -> ClusterContent(
+                    component = child.component,
+                    modifier = Modifier.fillMaxWidth()
+                        .verticalScroll(scrollState),
+                )
+                is MainComponent.Child.Configsets -> ConfigsetsContent(
+                    component = child.component,
+                    modifier = Modifier.fillMaxWidth()
+                        .verticalScroll(scrollState),
+                )
                 is MainComponent.Child.Environment -> EnvironmentContent(
                     component = child.component,
                     modifier = Modifier.fillMaxWidth()
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/navigation/NavigationSideBar.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/navigation/NavigationSideBar.kt
index e0de0b4d55d..201cf73b633 100644
--- 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/navigation/NavigationSideBar.kt
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/navigation/NavigationSideBar.kt
@@ -190,6 +190,8 @@ private fun getMenuIcon(item: MainMenu) = when (item) {
  * TODO Remove once all sections are added
  */
 private fun isSectionAvailable(item: MainMenu): Boolean = when (item) {
+    MainMenu.Cluster -> true
+    MainMenu.Configsets -> true
     MainMenu.Environment -> true
     MainMenu.Logging -> true
     else -> false
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/navigation/NavigationTabs.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/navigation/NavigationTabs.kt
new file mode 100644
index 00000000000..798e1794939
--- /dev/null
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/navigation/NavigationTabs.kt
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.ui.views.navigation
+
+import androidx.compose.material3.ScrollableTabRow
+import androidx.compose.material3.Tab
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import com.arkivanov.decompose.extensions.compose.subscribeAsState
+import kotlin.enums.EnumEntries
+import org.apache.solr.ui.components.navigation.TabNavigationComponent
+import org.jetbrains.compose.resources.StringResource
+import org.jetbrains.compose.resources.stringResource
+
+/**
+ * Navigation tabs that can be used for adding basic navigation elements to a 
section.
+ *
+ * @param component The tab navigation component that manages the tab 
navigation.
+ * @param entries The entries to display in the navigation.
+ * @param mapper Mapper function that maps the entries to string resources, 
used to render the tab
+ * labels.
+ * @param modifier Modifier to apply to the root element.
+ */
+@Composable
+fun <T : Enum<T>, C : Any> NavigationTabs(
+    component: TabNavigationComponent<T, C>,
+    entries: EnumEntries<T>, // TODO See if we can skip this parameter
+    mapper: (T) -> StringResource,
+    modifier: Modifier = Modifier,
+) {
+    val slot by component.tabSlot.subscribeAsState()
+
+    val currentTab = slot.child?.configuration?.tab
+    val currentTabIndex = currentTab?.ordinal ?: 0
+
+    ScrollableTabRow(
+        modifier = modifier,
+        selectedTabIndex = currentTabIndex,
+        edgePadding = 16.dp,
+    ) {
+        entries.forEach { tab ->
+            val selected = currentTab == tab
+
+            Tab(
+                selected = selected,
+                onClick = { component.onNavigate(tab) },
+                text = {
+                    Text(
+                        text = stringResource(mapper(tab)),
+                        maxLines = 1,
+                        overflow = TextOverflow.Ellipsis,
+                    )
+                },
+            )
+        }
+    }
+}
diff --git 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/navigation/configsets/ConfigsetsTab.kt
similarity index 64%
copy from 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
copy to 
solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/navigation/configsets/ConfigsetsTab.kt
index 19557f36d01..3a68a7cd8df 100644
--- 
a/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/components/main/integration/Mappers.kt
+++ 
b/solr/ui/src/commonMain/kotlin/org/apache/solr/ui/views/navigation/configsets/ConfigsetsTab.kt
@@ -14,15 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.solr.ui.views.navigation.configsets
 
-package org.apache.solr.ui.components.main.integration
+import kotlinx.serialization.Serializable
 
-import org.apache.solr.ui.components.main.MainComponent
-import org.apache.solr.ui.views.navigation.MainMenu
-
-val MainComponent.Child.asMainMenu: MainMenu
-    get() = when (this) {
-        // TODO Add additional mappings once more children are supported
-        is MainComponent.Child.Environment -> MainMenu.Environment
-        is MainComponent.Child.Logging -> MainMenu.Logging
-    }
+@Serializable
+enum class ConfigsetsTab {
+    Overview,
+    Files,
+    Schema,
+    UpdateConfig,
+    IndexQuery,
+    Handlers,
+    SearchComponents,
+}
diff --git 
a/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/TestAppComponentContext.kt 
b/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/TestAppComponentContext.kt
new file mode 100644
index 00000000000..d78088ee834
--- /dev/null
+++ 
b/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/TestAppComponentContext.kt
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.ui
+
+import com.arkivanov.decompose.ComponentContext
+import com.arkivanov.decompose.ComponentContextFactory
+import com.arkivanov.decompose.DefaultComponentContext
+import com.arkivanov.essenty.backhandler.BackHandlerOwner
+import com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner
+import com.arkivanov.essenty.lifecycle.LifecycleOwner
+import com.arkivanov.essenty.lifecycle.LifecycleRegistry
+import com.arkivanov.essenty.statekeeper.StateKeeperOwner
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import org.apache.solr.ui.utils.AppComponentContext
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class TestAppComponentContext(
+    scheduler: TestCoroutineScheduler,
+    lifecycle: LifecycleRegistry = LifecycleRegistry(),
+    componentContext: ComponentContext = DefaultComponentContext(lifecycle = 
lifecycle),
+    override val mainContext: CoroutineContext = 
StandardTestDispatcher(scheduler),
+    override val ioContext: CoroutineContext = 
UnconfinedTestDispatcher(scheduler),
+) : AppComponentContext,
+    LifecycleOwner by componentContext,
+    StateKeeperOwner by componentContext,
+    InstanceKeeperOwner by componentContext,
+    BackHandlerOwner by componentContext {
+
+    override val componentContextFactory: 
ComponentContextFactory<AppComponentContext> =
+        ComponentContextFactory { lifecycle, stateKeeper, instanceKeeper, 
backHandler ->
+            val ctx = componentContext.componentContextFactory(
+                lifecycle = lifecycle,
+                stateKeeper = stateKeeper,
+                instanceKeeper = instanceKeeper,
+                backHandler = backHandler,
+            )
+            TestAppComponentContext(
+                scheduler = scheduler,
+                componentContext = ctx,
+            )
+        }
+}
diff --git a/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/TestUtils.kt 
b/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/TestUtils.kt
index a157216b185..167fb25133a 100644
--- a/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/TestUtils.kt
+++ b/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/TestUtils.kt
@@ -17,11 +17,15 @@
 
 package org.apache.solr.ui
 
+import io.ktor.client.HttpClient
 import io.ktor.client.engine.mock.MockEngine
 import io.ktor.client.engine.mock.MockEngineConfig
 import io.ktor.client.engine.mock.MockRequestHandler
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
+import io.ktor.serialization.kotlinx.json.json
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.serialization.json.Json
 
 /**
  * Creates a mock engine with the given handlers and a standard test 
dispatcher.
@@ -40,3 +44,19 @@ fun TestScope.createMockEngine(
         this.reuseHandlers = reuseHandlers
     },
 )
+
+/**
+ * Provides an HTTP client configured to use JSON content negotiation.
+ *
+ * @param engine The mock engine that contains handlers with HTTP JSON 
responses.
+ */
+fun testHttpClient(engine: MockEngine) = HttpClient(engine) {
+    install(ContentNegotiation) {
+        json(
+            Json {
+                ignoreUnknownKeys = true
+                allowSpecialFloatingPointValues = true
+            },
+        )
+    }
+}
diff --git 
a/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/components/configsets/DefaultConfigsetsComponentIntegrationTest.kt
 
b/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/components/configsets/DefaultConfigsetsComponentIntegrationTest.kt
new file mode 100644
index 00000000000..c6581f4d020
--- /dev/null
+++ 
b/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/components/configsets/DefaultConfigsetsComponentIntegrationTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.ui.components.configsets
+
+import com.arkivanov.essenty.lifecycle.LifecycleRegistry
+import com.arkivanov.essenty.lifecycle.resume
+import com.arkivanov.mvikotlin.core.store.StoreFactory
+import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
+import io.ktor.client.HttpClient
+import io.ktor.client.engine.mock.MockRequestHandleScope
+import io.ktor.client.engine.mock.MockRequestHandler
+import io.ktor.client.engine.mock.respond
+import io.ktor.client.request.HttpRequestData
+import io.ktor.http.ContentType
+import io.ktor.http.HttpHeaders
+import io.ktor.http.HttpStatusCode
+import io.ktor.http.headersOf
+import kotlin.test.Test
+import kotlin.test.assertContains
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import kotlinx.serialization.json.Json
+import org.apache.solr.ui.TestAppComponentContext
+import org.apache.solr.ui.components.configsets.data.ListConfigsets
+import 
org.apache.solr.ui.components.configsets.integration.DefaultConfigsetsComponent
+import org.apache.solr.ui.components.start.integration.DefaultStartComponent
+import org.apache.solr.ui.createMockEngine
+import org.apache.solr.ui.domain.Configset
+import org.apache.solr.ui.testHttpClient
+import org.apache.solr.ui.utils.AppComponentContext
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class DefaultConfigsetsComponentIntegrationTest {
+
+    private val configset1 = "_default"
+    private val configset2 = "techproducts"
+    private val configset3 = "getting_started"
+    private val configSets = listOf(configset1, configset2, configset3)
+
+    /**
+     * Request handler that returns a list of configsets
+     */
+    private val configsetsResponseHandler: MockRequestHandler = { scope: 
MockRequestHandleScope, data: HttpRequestData ->
+        val content = ListConfigsets(configSets)
+        scope.respond(
+            content = Json.encodeToString(value = content),
+            status = HttpStatusCode.OK,
+            headers = headersOf(
+                name = HttpHeaders.ContentType,
+                value = ContentType.Application.Json.toString(),
+            ),
+        )
+    }
+
+    @Test
+    fun `GIVEN configsets WHEN initialized THEN configsets fetched`() = 
runTest {
+        val engine = createMockEngine(configsetsResponseHandler)
+        val component = createComponent(httpClient = testHttpClient(engine))
+        advanceUntilIdle()
+
+        component.model.value.configsets.forEach { configset ->
+            assertContains(
+                iterable = configSets,
+                element = configset.name,
+            )
+        }
+    }
+
+    @Test
+    fun `GIVEN configsets WHEN configsets fetched THEN configsets sorted`() = 
runTest {
+        val engine = createMockEngine(configsetsResponseHandler)
+        val component = createComponent(httpClient = testHttpClient(engine))
+        advanceUntilIdle()
+
+        assertContentEquals(
+            expected = configSets.sorted(),
+            actual = component.model.value.configsets.map(Configset::name),
+        )
+    }
+
+    @Test
+    fun `GIVEN configsets WHEN configset selected THEN selectedConfigset 
updated`() = runTest {
+        val engine = createMockEngine(configsetsResponseHandler)
+        val component = createComponent(httpClient = testHttpClient(engine))
+        advanceUntilIdle()
+
+        component.onSelectConfigset(name = configset2)
+        advanceUntilIdle()
+
+        assertEquals(
+            expected = configset2,
+            actual = component.model.value.selectedConfigset,
+        )
+    }
+
+    /**
+     * Helper function for creating an instance of the [DefaultStartComponent].
+     */
+    private fun TestScope.createComponent(
+        componentContext: AppComponentContext = 
TestAppComponentContext(scheduler = testScheduler),
+        storeFactory: StoreFactory = DefaultStoreFactory(),
+        httpClient: HttpClient = HttpClient(),
+    ): ConfigsetsComponent {
+        val lifecycle = LifecycleRegistry()
+
+        val component = DefaultConfigsetsComponent(
+            componentContext = componentContext,
+            storeFactory = storeFactory,
+            httpClient = httpClient,
+        )
+
+        lifecycle.resume()
+        return component
+    }
+}
diff --git 
a/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/components/navigation/DefaultTabNavigationComponentIntegrationTest.kt
 
b/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/components/navigation/DefaultTabNavigationComponentIntegrationTest.kt
new file mode 100644
index 00000000000..43c0a46b6ce
--- /dev/null
+++ 
b/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/components/navigation/DefaultTabNavigationComponentIntegrationTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.ui.components.navigation
+
+import com.arkivanov.decompose.router.slot.child
+import com.arkivanov.essenty.lifecycle.Lifecycle
+import com.arkivanov.essenty.lifecycle.LifecycleRegistry
+import com.arkivanov.essenty.lifecycle.create
+import com.arkivanov.essenty.lifecycle.resume
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.builtins.serializer
+import org.apache.solr.ui.TestAppComponentContext
+import 
org.apache.solr.ui.components.navigation.TabNavigationComponent.Configuration
+import 
org.apache.solr.ui.components.navigation.integration.DefaultTabNavigationComponent
+import org.apache.solr.ui.components.start.integration.DefaultStartComponent
+import org.apache.solr.ui.utils.AppComponentContext
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class DefaultTabNavigationComponentIntegrationTest {
+
+    @Serializable
+    private enum class TestNavigationTab {
+        Tab1,
+        Tab2,
+    }
+
+    private data class TabChild(
+        val configuration: Configuration<TestNavigationTab>,
+        val context: AppComponentContext? = null,
+    )
+
+    @Test
+    fun `GIVEN initial tab WHEN initialized THEN childFactory called with 
initial tab`() = runTest {
+        val initialTab = TestNavigationTab.Tab1
+        var called = false
+        val component = createComponent(
+            initialTab = initialTab,
+            tabSerializer = TestNavigationTab.serializer(),
+            childFactory = { configuration, context ->
+                called = true
+                TabChild(configuration, context)
+            },
+        )
+
+        advanceUntilIdle()
+        assertNotNull(actual = component.tabSlot.child?.instance)
+        assertEquals(
+            expected = TestNavigationTab.Tab1,
+            actual = component.tabSlot.child?.configuration?.tab,
+        )
+        assertTrue(actual = called, message = "child factory never called")
+    }
+
+    @Test
+    fun `GIVEN a selection WHEN navigate to other tab THEN other tab 
selected`() = runTest {
+        val expectedTab = TestNavigationTab.Tab2
+        val component = createComponent(
+            initialTab = TestNavigationTab.Tab1,
+            tabSerializer = TestNavigationTab.serializer(),
+            childFactory = { configuration, context -> TabChild(configuration, 
context) },
+        )
+        advanceUntilIdle()
+
+        component.onNavigate(expectedTab)
+        advanceUntilIdle()
+
+        assertEquals(
+            expected = expectedTab,
+            actual = component.tabSlot.child?.configuration?.tab,
+        )
+    }
+
+    @Test
+    fun `GIVEN a selection WHEN navigate to same tab THEN childFactory not 
called again`() = runTest {
+        val initialTab = TestNavigationTab.Tab1
+        var called = false
+        val component = createComponent(
+            initialTab = initialTab,
+            tabSerializer = TestNavigationTab.serializer(),
+            childFactory = { configuration, context ->
+                if (!called) {
+                    called = true
+                } else {
+                    fail("Should not be called twice")
+                }
+                TabChild(configuration, context)
+            },
+        )
+
+        component.onNavigate(initialTab)
+        advanceUntilIdle()
+    }
+
+    /**
+     * Helper function for creating an instance of the [DefaultStartComponent].
+     */
+    private fun <T : Enum<T>, C : Any> TestScope.createComponent(
+        componentContext: AppComponentContext = 
TestAppComponentContext(scheduler = testScheduler),
+        lifecycle: LifecycleRegistry = LifecycleRegistry(),
+        initialTab: T,
+        tabSerializer: KSerializer<T>,
+        childFactory: (Configuration<T>, AppComponentContext) -> C,
+    ): TabNavigationComponent<T, C> {
+        val component = DefaultTabNavigationComponent(
+            componentContext = componentContext,
+            tabSerializer = tabSerializer,
+            initialTab = initialTab,
+            childFactory = childFactory,
+        )
+
+        lifecycle.resume()
+        return component
+    }
+}
diff --git 
a/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/views/configsets/ConfigsetsContentTest.kt
 
b/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/views/configsets/ConfigsetsContentTest.kt
new file mode 100644
index 00000000000..821f08a5ad2
--- /dev/null
+++ 
b/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/views/configsets/ConfigsetsContentTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.ui.views.configsets
+
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.runComposeUiTest
+import com.arkivanov.decompose.Child
+import com.arkivanov.decompose.router.slot.ChildSlot
+import com.arkivanov.decompose.value.MutableValue
+import com.arkivanov.decompose.value.Value
+import kotlin.test.Ignore
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import org.apache.solr.ui.components.configsets.ConfigsetsComponent
+import org.apache.solr.ui.components.configsets.ConfigsetsComponent.Model
+import 
org.apache.solr.ui.components.configsets.overview.ConfigsetsOverviewComponent
+import 
org.apache.solr.ui.components.navigation.TabNavigationComponent.Configuration
+import org.apache.solr.ui.domain.Configset
+import org.apache.solr.ui.views.navigation.configsets.ConfigsetsTab
+
+@OptIn(ExperimentalTestApi::class)
+class ConfigsetsContentTest {
+
+    @Test
+    @Ignore // See why the placeholder text is not shown
+    fun `GIVEN no configsets THEN no_configsets_placeholder is shown`() = 
runComposeUiTest {
+        val component = TestConfigsetsComponent()
+
+        setContent { ConfigsetsContent(component = component) }
+
+        // Placeholder text from the TextField
+        onNodeWithTag(testTag = "no_configsets_placeholder").assertExists()
+    }
+
+    @Test
+    fun `GIVEN configsets WHEN a configset selected THEN onSelectConfigset 
called with configset`() = runComposeUiTest {
+        val selectedConfigset = "gettingstarted"
+        val expectedConfigsetSelection = "techproducts"
+        val component = TestConfigsetsComponent(
+            model = Model(
+                configsets = listOf(selectedConfigset, 
expectedConfigsetSelection)
+                    .map { Configset(it) },
+                selectedConfigset = selectedConfigset,
+            ),
+        )
+
+        setContent { ConfigsetsContent(component = component) }
+
+        // Expand menu and select expected configset
+        onNodeWithTag(testTag = "configsets_dropdown").performClick()
+        onNodeWithTag(testTag = expectedConfigsetSelection).performClick()
+
+        waitForIdle()
+        assertEquals(
+            expected = expectedConfigsetSelection,
+            actual = component.onSelectConfigset,
+        )
+    }
+}
+
+class TestConfigsetsComponent(
+    model: Model = Model(),
+) : ConfigsetsComponent {
+
+    var onSelectConfigset: String? = model.selectedConfigset
+    override val model: StateFlow<Model> = MutableStateFlow(model)
+
+    private val overviewChild =
+        ConfigsetsComponent.Child.Overview(object : 
ConfigsetsOverviewComponent {})
+
+    override val tabSlot: Value<ChildSlot<Configuration<ConfigsetsTab>, 
ConfigsetsComponent.Child>> = MutableValue(
+        ChildSlot(
+            Child.Created(
+                configuration = Configuration(tab = ConfigsetsTab.Overview),
+                instance = overviewChild,
+            ),
+        ),
+    )
+
+    override fun onNavigate(tab: ConfigsetsTab) {
+        // Tested in TabNavigationTest (no need to test here)
+    }
+
+    override fun onSelectConfigset(name: String) {
+        onSelectConfigset = name
+    }
+}
diff --git 
a/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/views/configsets/ConfigsetsDropdownMenuTest.kt
 
b/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/views/configsets/ConfigsetsDropdownMenuTest.kt
new file mode 100644
index 00000000000..81e1746e3ad
--- /dev/null
+++ 
b/solr/ui/src/commonTest/kotlin/org/apache/solr/ui/views/configsets/ConfigsetsDropdownMenuTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.ui.views.configsets
+
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.isDisplayed
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.runComposeUiTest
+import kotlin.test.Test
+import org.apache.solr.ui.components.configsets.ConfigsetsComponent.Model
+import org.apache.solr.ui.domain.Configset
+
+@OptIn(ExperimentalTestApi::class)
+class ConfigsetsDropdownMenuTest {
+
+    @Test
+    fun `GIVEN empty availableConfigsets WHEN dropdown clicked THEN not 
expanded`() = runComposeUiTest {
+        setContent {
+            ConfigsetsDropdown(
+                selectConfigset = {},
+                availableConfigsets = emptyList(),
+                selectedConfigSet = "",
+            )
+        }
+
+        // Field click shouldn’t open the menu (anchor is disabled in our impl)
+        onNodeWithTag(testTag = "configsets_dropdown").performClick()
+        onNodeWithTag(testTag = 
"configsets_exposed_dropdown_menu").assertDoesNotExist()
+    }
+
+    @Test
+    fun `GIVEN empty availableConfigsets THEN dropdown disabled`() = 
runComposeUiTest {
+        setContent {
+            ConfigsetsDropdown(
+                selectConfigset = {},
+                availableConfigsets = emptyList(),
+                selectedConfigSet = "",
+            )
+        }
+
+        // Field click shouldn’t open the menu (anchor is disabled in our impl)
+        onNodeWithTag(testTag = "configsets_dropdown").assertIsNotEnabled()
+    }
+
+    @Test
+    fun `GIVEN configsets WHEN clicking dropdown THEN dropdown expands`() = 
runComposeUiTest {
+        val selectedConfigset = "gettingstarted"
+        val component = TestConfigsetsComponent(
+            model = Model(
+                configsets = listOf(selectedConfigset, "techproducts")
+                    .map { Configset(it) },
+                selectedConfigset = selectedConfigset,
+            ),
+        )
+
+        setContent { ConfigsetsContent(component = component) }
+
+        onNodeWithTag(testTag = "configsets_dropdown").performClick()
+        onNodeWithTag(testTag = 
"configsets_exposed_dropdown_menu").isDisplayed()
+    }
+}
diff --git 
a/solr/ui/src/desktopMain/kotlin/org/apache/solr/ui/preview/configsets/PreviewConfigsetsContent.kt
 
b/solr/ui/src/desktopMain/kotlin/org/apache/solr/ui/preview/configsets/PreviewConfigsetsContent.kt
new file mode 100644
index 00000000000..ba0dcb6bda1
--- /dev/null
+++ 
b/solr/ui/src/desktopMain/kotlin/org/apache/solr/ui/preview/configsets/PreviewConfigsetsContent.kt
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.ui.preview.configsets
+
+import androidx.compose.runtime.Composable
+import com.arkivanov.decompose.Child
+import com.arkivanov.decompose.router.slot.ChildSlot
+import com.arkivanov.decompose.value.MutableValue
+import com.arkivanov.decompose.value.Value
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import org.apache.solr.ui.components.configsets.ConfigsetsComponent
+import org.apache.solr.ui.components.configsets.ConfigsetsComponent.Model
+import 
org.apache.solr.ui.components.configsets.overview.ConfigsetsOverviewComponent
+import 
org.apache.solr.ui.components.navigation.TabNavigationComponent.Configuration
+import org.apache.solr.ui.domain.Configset
+import org.apache.solr.ui.preview.PreviewContainer
+import org.apache.solr.ui.views.configsets.ConfigsetsContent
+import org.apache.solr.ui.views.navigation.configsets.ConfigsetsTab
+import org.jetbrains.compose.ui.tooling.preview.Preview
+
+@Preview
+@Composable
+private fun PreviewConfigsetsContentEmptyConfigsets() = PreviewContainer {
+    ConfigsetsContent(component = SimplePreviewConfigsetsComponent())
+}
+
+@Preview
+@Composable
+private fun PreviewConfigsetsContentWithConfigsetSelected() = PreviewContainer 
{
+    val configset = "techproducts"
+    ConfigsetsContent(
+        component = SimplePreviewConfigsetsComponent(
+            model = Model(
+                configsets = listOf(configset, "getting_started").map { 
Configset(name = it) },
+                selectedConfigset = configset,
+            ),
+        ),
+    )
+}
+
+private class SimplePreviewConfigsetsComponent(model: Model = Model()) : 
ConfigsetsComponent {
+    override val model: StateFlow<Model> = MutableStateFlow(model)
+
+    override fun onSelectConfigset(name: String) = Unit
+
+    override val tabSlot: Value<ChildSlot<Configuration<ConfigsetsTab>, 
ConfigsetsComponent.Child>>
+        get() = MutableValue(
+            initialValue = ChildSlot(
+                Child.Created(
+                    configuration = Configuration(tab = 
ConfigsetsTab.Overview),
+                    instance = 
ConfigsetsComponent.Child.Overview(PreviewConfigsetsOverviewComponent),
+                ),
+            ),
+        )
+
+    override fun onNavigate(tab: ConfigsetsTab) = Unit
+}
+
+private object PreviewConfigsetsOverviewComponent : ConfigsetsOverviewComponent


Reply via email to