change(design) : introduce PreferenceDropdown component

This commit is contained in:
ganfra 2025-01-17 09:51:56 +01:00
parent 007555eb56
commit fd3b99765d

View file

@ -0,0 +1,189 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
@file:OptIn(ExperimentalMaterial3Api::class)
package io.element.android.libraries.designsystem.components.preferences
import androidx.annotation.DrawableRes
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.MenuAnchorType
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.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.components.list.ListItemContent
import io.element.android.libraries.designsystem.components.preferences.components.preferenceIcon
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem
import io.element.android.libraries.designsystem.theme.components.ListItem
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.toEnabledColor
import io.element.android.libraries.designsystem.toSecondaryEnabledColor
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@Composable
fun <T : DropdownOption> PreferenceDropdown(
title: String,
selectedOption: T?,
options: ImmutableList<T>,
onSelectOption: (T) -> Unit,
modifier: Modifier = Modifier,
supportingText: String? = null,
enabled: Boolean = true,
icon: ImageVector? = null,
@DrawableRes iconResourceId: Int? = null,
showIconAreaIfNoIcon: Boolean = false,
) {
var isDropdownExpanded by remember { mutableStateOf(false) }
ListItem(
modifier = modifier,
leadingContent = preferenceIcon(
icon = icon,
iconResourceId = iconResourceId,
enabled = enabled,
showIconAreaIfNoIcon = showIconAreaIfNoIcon,
),
headlineContent = {
Text(
style = ElementTheme.typography.fontBodyLgRegular,
modifier = Modifier.fillMaxWidth(),
text = title,
color = enabled.toEnabledColor(),
)
},
supportingContent = supportingText?.let {
{
Text(
style = ElementTheme.typography.fontBodyMdRegular,
text = it,
color = enabled.toSecondaryEnabledColor(),
)
}
},
trailingContent = ListItemContent.Custom(
content = {
DropdownTrailingContent(
selectedOption = selectedOption,
options = options,
onSelectOption = onSelectOption,
expanded = isDropdownExpanded,
onExpandedChange = { isDropdownExpanded = it },
)
}
),
onClick = { isDropdownExpanded = true }.takeIf { !isDropdownExpanded },
)
}
/**
* A dropdown option that can be used in a [PreferenceDropdown].
*/
interface DropdownOption {
/**
* The text to display for this option.
*/
val text: String
}
@Composable
private fun <T : DropdownOption> DropdownTrailingContent(
selectedOption: T?,
options: ImmutableList<T>,
expanded: Boolean,
onExpandedChange: (Boolean) -> Unit,
onSelectOption: (T) -> Unit,
modifier: Modifier = Modifier,
) {
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = onExpandedChange,
modifier = modifier,
) {
Row(
modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = selectedOption?.text.orEmpty(),
maxLines = 1,
style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.colors.textSecondary,
)
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
}
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { onExpandedChange(false) },
matchTextFieldWidth = false,
) {
options.forEach { option ->
DropdownMenuItem(
text = {
Text(
text = option.text,
style = ElementTheme.typography.fontBodyMdRegular
)
},
onClick = {
onSelectOption(option)
onExpandedChange(false)
},
)
}
}
}
}
@Preview(group = PreviewGroup.Preferences)
@Composable
internal fun PreferenceDropdownPreview() = ElementThemedPreview {
val options = listOf(
object : DropdownOption {
override val text = "Option 1"
},
object : DropdownOption {
override val text = "Option 2"
},
object : DropdownOption {
override val text = "Option 3"
},
).toImmutableList()
Column {
PreferenceDropdown(
title = "Dropdown",
supportingText = "Options for dropdown",
icon = CompoundIcons.Threads(),
selectedOption = null,
options = options,
onSelectOption = {},
)
PreferenceDropdown(
title = "Dropdown",
supportingText = "Options for dropdown",
icon = CompoundIcons.Threads(),
selectedOption = options.first(),
options = options,
onSelectOption = {},
)
}
}