change(design) : introduce PreferenceDropdown component
This commit is contained in:
parent
007555eb56
commit
fd3b99765d
1 changed files with 189 additions and 0 deletions
|
|
@ -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 = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue