diff --git a/.github/workflows/deploy.yml b/.github/workflows/docs.yml similarity index 100% rename from .github/workflows/deploy.yml rename to .github/workflows/docs.yml diff --git a/example/src/commonMain/kotlin/io/github/komodgn/example/Application.kt b/example/src/commonMain/kotlin/io/github/komodgn/example/Application.kt index 8f0cbb5..48da641 100644 --- a/example/src/commonMain/kotlin/io/github/komodgn/example/Application.kt +++ b/example/src/commonMain/kotlin/io/github/komodgn/example/Application.kt @@ -15,15 +15,18 @@ */ package io.github.komodgn.example +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.windowInsetsPadding @@ -34,7 +37,6 @@ import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier @@ -50,10 +52,11 @@ import io.github.komodgn.example.util.getInitialCode @Composable fun App() { + val scrollState = rememberScrollState() var isDark by rememberSaveable { mutableStateOf(true) } var selectedComponent by rememberSaveable { mutableStateOf(DemoComponent.CODE_VIEW) } - var currentLang by remember { mutableStateOf(CodeLanguage.KOTLIN) } - var userInput by remember { mutableStateOf(getInitialCode(currentLang, AppConfig.LIBRARY_VERSION)) } + var currentLang by rememberSaveable { mutableStateOf(CodeLanguage.KOTLIN) } + var userInput by rememberSaveable { mutableStateOf(getInitialCode(currentLang, AppConfig.LIBRARY_VERSION)) } CodeViewTheme(isDarkTheme = isDark) { Scaffold( @@ -62,6 +65,7 @@ fun App() { contentWindowInsets = WindowInsets(0, 0, 0, 0), topBar = { DemoTopBar( + scrollState = scrollState, isDark = isDark, onToggleTheme = { isDark = !isDark }, selectedComponent = selectedComponent, @@ -74,10 +78,11 @@ fun App() { .fillMaxSize() .padding(innerPadding) .background(MaterialTheme.colorScheme.background) - .windowInsetsPadding(WindowInsets.safeDrawing) + .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom)) .imePadding(), ) { MainContent( + scrollState = scrollState, isCompact = maxWidth < 600.dp, selectedDemoComponent = selectedComponent, code = userInput, @@ -95,6 +100,7 @@ fun App() { @Composable private fun MainContent( + scrollState: ScrollState, isCompact: Boolean, selectedDemoComponent: DemoComponent, code: String, @@ -102,8 +108,6 @@ private fun MainContent( onCodeChange: (String) -> Unit, onLanguageChange: (CodeLanguage) -> Unit, ) { - val scrollState = rememberScrollState() - Column( modifier = Modifier .fillMaxWidth() diff --git a/example/src/commonMain/kotlin/io/github/komodgn/example/component/DemoComponentFilterRow.kt b/example/src/commonMain/kotlin/io/github/komodgn/example/component/DemoComponentFilterRow.kt new file mode 100644 index 0000000..5df8536 --- /dev/null +++ b/example/src/commonMain/kotlin/io/github/komodgn/example/component/DemoComponentFilterRow.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2026 komodgn + * + * Licensed 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 io.github.komodgn.example.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import io.github.komodgn.example.DemoComponent + +@Composable +fun DemoComponentFilterRow( + selectedComponent: DemoComponent, + onComponentSelect: (DemoComponent) -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + DemoComponent.entries.forEach { component -> + val isSelected = selectedComponent == component + DemoFilterChip( + label = when (component) { + DemoComponent.CODE_VIEW -> "CodeView" + DemoComponent.CODE_EDITOR -> "CodeEditor" + }, + isSelected = isSelected, + onClick = { onComponentSelect(component) }, + ) + } + } +} diff --git a/example/src/commonMain/kotlin/io/github/komodgn/example/component/DemoFilterChip.kt b/example/src/commonMain/kotlin/io/github/komodgn/example/component/DemoFilterChip.kt new file mode 100644 index 0000000..5c600d6 --- /dev/null +++ b/example/src/commonMain/kotlin/io/github/komodgn/example/component/DemoFilterChip.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2026 komodgn + * + * Licensed 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 io.github.komodgn.example.component + +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FilterChipDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun DemoFilterChip( + label: String, + isSelected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + FilterChip( + selected = isSelected, + onClick = onClick, + label = { Text(text = label) }, + modifier = modifier, + colors = FilterChipDefaults.filterChipColors( + labelColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.7f), + selectedLabelColor = MaterialTheme.colorScheme.primary, + selectedContainerColor = MaterialTheme.colorScheme.onPrimary, + ), + border = FilterChipDefaults.filterChipBorder( + enabled = true, + selected = isSelected, + borderColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.5f), + ), + ) +} diff --git a/example/src/commonMain/kotlin/io/github/komodgn/example/component/DemoTopBar.kt b/example/src/commonMain/kotlin/io/github/komodgn/example/component/DemoTopBar.kt index 10f2ce0..43f2df9 100644 --- a/example/src/commonMain/kotlin/io/github/komodgn/example/component/DemoTopBar.kt +++ b/example/src/commonMain/kotlin/io/github/komodgn/example/component/DemoTopBar.kt @@ -15,56 +15,96 @@ */ package io.github.komodgn.example.component +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.DarkMode import androidx.compose.material.icons.filled.LightMode -import androidx.compose.material3.FilterChip -import androidx.compose.material3.FilterChipDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import io.github.komodgn.example.DemoComponent @Composable fun DemoTopBar( + scrollState: ScrollState, isDark: Boolean, onToggleTheme: () -> Unit, selectedComponent: DemoComponent, onComponentSelect: (DemoComponent) -> Unit, ) { + val scrollThreshold = 120f + val getCollapseFraction = { (scrollState.value / scrollThreshold).coerceIn(0f, 1f) } + + val isFilterVisible by remember { + derivedStateOf { (scrollState.value / scrollThreshold) < 0.9f } + } + Column( modifier = Modifier .fillMaxWidth() .background(MaterialTheme.colorScheme.primary) - .statusBarsPadding(), + .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top)) + .padding(bottom = 8.dp), ) { - Row( + Box( modifier = Modifier .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp, top = 4.dp, bottom = 0.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, + .height(40.dp) + .padding(horizontal = 16.dp), + contentAlignment = Alignment.Center, ) { Text( text = "Compose CodeView Demo", fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onPrimary, - modifier = Modifier.padding(8.dp), + modifier = Modifier + .align(Alignment.CenterStart) + .graphicsLayer { + val fraction = getCollapseFraction() + alpha = 1f - fraction + translationY = -20f * fraction + }, + style = MaterialTheme.typography.titleMedium, ) - IconButton(onClick = onToggleTheme) { + + Text( + text = if (selectedComponent == DemoComponent.CODE_VIEW) "CodeView" else "CodeEditor", + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onPrimary, + modifier = Modifier + .graphicsLayer { + val fraction = getCollapseFraction() + alpha = fraction + translationY = 20f * (1f - fraction) + }, + style = MaterialTheme.typography.bodyMedium, + ) + + IconButton( + onClick = onToggleTheme, + modifier = Modifier.align(Alignment.CenterEnd), + ) { Icon( imageVector = if (isDark) Icons.Default.LightMode else Icons.Default.DarkMode, contentDescription = "Toggle Theme", @@ -73,37 +113,21 @@ fun DemoTopBar( } } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp, top = 0.dp, bottom = 8.dp), - horizontalArrangement = Arrangement.spacedBy(12.dp), - ) { - DemoComponent.entries.forEach { component -> - val isSelected = selectedComponent == component - FilterChip( - selected = isSelected, - onClick = { onComponentSelect(component) }, - label = { - Text( - text = when (component) { - DemoComponent.CODE_VIEW -> "CodeView" - DemoComponent.CODE_EDITOR -> "CodeEditor" - }, - ) + if (isFilterVisible) { + DemoComponentFilterRow( + selectedComponent = selectedComponent, + onComponentSelect = onComponentSelect, + modifier = Modifier + .fillMaxWidth() + .height(48.dp) + .padding(horizontal = 16.dp) + .graphicsLayer { + val fraction = getCollapseFraction() + alpha = (1f - fraction * 2f).coerceIn(0f, 1f) + scaleY = 1f - fraction + translationY = -10f * fraction }, - colors = FilterChipDefaults.filterChipColors( - labelColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.7f), - selectedLabelColor = MaterialTheme.colorScheme.primary, - selectedContainerColor = MaterialTheme.colorScheme.onPrimary, - ), - border = FilterChipDefaults.filterChipBorder( - enabled = true, - selected = isSelected, - borderColor = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.5f), - ), - ) - } + ) } } }