Skip to content

Instantly share code, notes, and snippets.

@avwie
Last active November 25, 2022 18:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save avwie/9b0ee5f2882e8d26517f4ab7fc2b0408 to your computer and use it in GitHub Desktop.
Save avwie/9b0ee5f2882e8d26517f4ab7fc2b0408 to your computer and use it in GitHub Desktop.
@Composable fun Sidebar(
expanded: Boolean = true,
content: @Composable SidebarScope.() -> Unit = {}
) {
Column (
verticalArrangement = Arrangement.spacedBy(Dimensions.DefaultPadding / 2),
modifier = Modifier
.doubleBorder()
.background(Colors.Sidebar.Background)
.padding(Dimensions.DefaultPadding)
) {
val scope = remember(expanded) { SidebarScope(expanded, this) }
content(scope)
}
}
class SidebarScope(val expanded: Boolean, private val columnScope: ColumnScope): ColumnScope by columnScope {
@OptIn(ExperimentalComposeUiApi::class, ExperimentalAnimationApi::class)
@Composable fun Item(
painter: Painter,
title: String,
isActive: Boolean = false
) {
val interactionSource = remember { MutableInteractionSource() }
val isHover by interactionSource.collectIsHoveredAsState()
val animatedBackgroundColor by animateColorAsState(
when {
isActive -> Colors.Sidebar.Item.Background.Active
isHover -> Colors.Sidebar.Item.Background.Hover
else -> Colors.Sidebar.Item.Background.Default
}
)
Box(
modifier = Modifier
.clip(RoundedCornerShape(Dimensions.DefaultCornerRadius / 2))
.background(animatedBackgroundColor)
.pointerHoverIcon(PointerIconDefaults.Hand)
.hoverable(interactionSource)
.padding(Dimensions.DefaultPadding)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
painter = painter,
contentDescription = title,
modifier = Modifier.size(Dimensions.Sidebar.ItemSize)
)
AnimatedContent(
targetState = this@SidebarScope.expanded
) { targetExpanded ->
if (targetExpanded) {
Text(
text = title,
modifier = Modifier
.padding(horizontal = Dimensions.DefaultPadding)
)
}
}
}
}
}
}
package nl.avwie.ui.demos.sidebar
import androidx.compose.animation.*
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.input.pointer.PointerIconDefaults
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.layout.boundsInParent
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.*
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupPositionProvider
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
@Composable fun Sidebar(
expanded: Boolean = true,
content: @Composable SidebarScope.() -> Unit = {}
) {
Column (
verticalArrangement = Arrangement.spacedBy(Dimensions.DefaultPadding / 2),
modifier = Modifier
.doubleBorder()
.background(Colors.Sidebar.Background)
.padding(Dimensions.DefaultPadding)
) {
val scope = remember(expanded) {
SidebarScope(expanded,this)
}
content(scope)
}
}
class SidebarScope(
val expanded: Boolean,
val columnScope: ColumnScope
): ColumnScope by columnScope {
private var sizes by mutableStateOf(mapOf<Any, Dp>())
private val largestSize by derivedStateOf { sizes.values.maxOrNull() }
private fun notifyWidth(key: Any, width: Dp) {
sizes += (key to width)
}
@OptIn(ExperimentalComposeUiApi::class, ExperimentalAnimationApi::class)
@Composable fun Item(
painter: Painter,
title: String,
isActive: Boolean = false
) {
val interactionSource = remember { MutableInteractionSource() }
val isHover by interactionSource.collectIsHoveredAsState()
val animatedBackgroundColor by animateColorAsState(
when {
isActive -> Colors.Sidebar.Item.Background.Active
isHover -> Colors.Sidebar.Item.Background.Hover
else -> Colors.Sidebar.Item.Background.Default
}
)
val widthModifier = remember(largestSize, expanded) {
when {
expanded && largestSize != null -> Modifier.defaultMinSize(minWidth = largestSize!!)
else -> Modifier
}
}
with (LocalDensity.current) {
Box(
modifier = Modifier
.onGloballyPositioned { coords ->
this@SidebarScope.notifyWidth(title, coords.boundsInParent().width.toDp())
}
.then(widthModifier)
.clip(RoundedCornerShape(Dimensions.DefaultCornerRadius / 2))
.background(animatedBackgroundColor)
.pointerHoverIcon(PointerIconDefaults.Hand)
.hoverable(interactionSource)
.padding(Dimensions.DefaultPadding)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = painter,
contentDescription = title,
modifier = Modifier.size(Dimensions.Sidebar.ItemSize)
)
AnimatedContent(
targetState = this@SidebarScope.expanded,
) { targetExpanded ->
if (targetExpanded) {
Text(
text = title,
modifier = Modifier
.padding(horizontal = Dimensions.DefaultPadding)
)
}
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment