[Compound] Platform components (Lists) (#990)
Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
committed by
GitHub
parent
41baa54f01
commit
b36667844d
1
changelog.d/990.misc
Normal file
1
changelog.d/990.misc
Normal file
@@ -0,0 +1 @@
|
||||
Compound: add `ListItem` and `ListSectionHeader` components.
|
||||
@@ -26,6 +26,8 @@ object PreviewGroup {
|
||||
const val Dividers = "Dividers"
|
||||
const val FABs = "Floating Action Buttons"
|
||||
const val Icons = "Icons"
|
||||
const val ListItems = "List items"
|
||||
const val ListSections = "List sections"
|
||||
const val Menus = "Menus"
|
||||
const val Preferences = "Preferences"
|
||||
const val Progress = "Progress Indicators"
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* 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.element.android.libraries.designsystem.theme.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Share
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
|
||||
// Designs: https://www.figma.com/file/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?type=design&node-id=425%3A24208&mode=design&t=G5hCfkLB6GgXDuWe-1
|
||||
|
||||
/**
|
||||
* A List Item component to be used in lists and menus with simple layouts, matching the Material 3 guidelines.
|
||||
* @param headlineContent The main content of the list item, usually a text.
|
||||
* @param modifier The modifier to be applied to the list item.
|
||||
* @param supportingContent The content to be displayed below the headline content.
|
||||
* @param leadingContent The content to be displayed before the headline content.
|
||||
* @param trailingContent The content to be displayed after the headline content.
|
||||
* @param style The style to use for the list item. This may change the color and text styles of the contents. [ListItemStyle.Default] is used by default.
|
||||
* @param enabled Whether the list item is enabled. When disabled, will change the color of the headline content and the leading content to use disabled tokens.
|
||||
* @param onClick The callback to be called when the list item is clicked.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun ListItem(
|
||||
headlineContent: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
supportingContent: @Composable (() -> Unit)? = null,
|
||||
leadingContent: @Composable (() -> Unit)? = null,
|
||||
trailingContent: @Composable (() -> Unit)? = null,
|
||||
style: ListItemStyle = ListItemStyle.Default,
|
||||
enabled: Boolean = true,
|
||||
onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
val headlineColor = if (enabled) when (style) {
|
||||
ListItemStyle.Destructive -> ElementTheme.colors.textCriticalPrimary
|
||||
else -> ElementTheme.colors.textPrimary
|
||||
} else {
|
||||
// We cannot apply a disabled color by default: https://issuetracker.google.com/issues/280480132
|
||||
ElementTheme.colors.textDisabled
|
||||
}
|
||||
|
||||
val supportingContentColor = if (enabled) {
|
||||
ElementTheme.materialColors.onSurfaceVariant
|
||||
} else {
|
||||
// We cannot apply a disabled color by default: https://issuetracker.google.com/issues/280480132
|
||||
ElementTheme.colors.textDisabled
|
||||
}
|
||||
|
||||
val leadingTrailingContentColor = if (enabled) when (style) {
|
||||
ListItemStyle.Primary -> ElementTheme.colors.iconPrimary
|
||||
ListItemStyle.Destructive -> ElementTheme.colors.iconCriticalPrimary
|
||||
else -> ElementTheme.colors.iconTertiary
|
||||
} else {
|
||||
// We cannot apply a disabled color by default: https://issuetracker.google.com/issues/280480132
|
||||
ElementTheme.colors.iconDisabled
|
||||
}
|
||||
|
||||
val decoratedHeadlineContent: @Composable () -> Unit = {
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides ElementTheme.materialTypography.bodyLarge,
|
||||
LocalContentColor provides headlineColor,
|
||||
) {
|
||||
headlineContent()
|
||||
}
|
||||
}
|
||||
val decoratedSupportingContent: (@Composable () -> Unit)? = supportingContent?.let { content ->
|
||||
{
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides ElementTheme.materialTypography.bodyMedium,
|
||||
LocalContentColor provides supportingContentColor,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
val decoratedLeadingContent: (@Composable () -> Unit)? = leadingContent?.let { content ->
|
||||
{
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides leadingTrailingContentColor,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
val decoratedTrailingContent: (@Composable () -> Unit)? = trailingContent?.let { content ->
|
||||
{
|
||||
CompositionLocalProvider(
|
||||
LocalContentColor provides leadingTrailingContentColor,
|
||||
LocalTextStyle provides ElementTheme.typography.fontBodyMdRegular,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
androidx.compose.material3.ListItem(
|
||||
headlineContent = decoratedHeadlineContent,
|
||||
modifier = modifier.clickable(enabled = enabled && onClick != null, onClick = onClick ?: {}),
|
||||
overlineContent = null,
|
||||
supportingContent = decoratedSupportingContent,
|
||||
leadingContent = decoratedLeadingContent,
|
||||
trailingContent = decoratedTrailingContent,
|
||||
colors = ListItemDefaults.colors(), // These aren't really used since we need the workaround for the disabled state color
|
||||
tonalElevation = 0.dp,
|
||||
shadowElevation = 0.dp,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The style to use for a [ListItem].
|
||||
*/
|
||||
sealed interface ListItemStyle {
|
||||
object Default : ListItemStyle
|
||||
object Primary: ListItemStyle
|
||||
object Destructive : ListItemStyle
|
||||
}
|
||||
|
||||
// region: Simple list item
|
||||
@Preview(name = "List item (3 lines) - Simple", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemThreeLinesSimplePreview() = PreviewItems.ThreeLinesListItemPreview()
|
||||
|
||||
@Preview(name = "List item (2 lines) - Simple", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemTwoLinesSimplePreview() = PreviewItems.TwoLinesListItemPreview()
|
||||
|
||||
@Preview(name = "List item (1 line) - Simple", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemSingleLineSimplePreview() = PreviewItems.OneLineListItemPreview()
|
||||
// endregion
|
||||
|
||||
// region: Trailing Checkbox
|
||||
@Preview(name = "List item (3 lines) - Trailing Checkbox", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemThreeLinesTrailingCheckBoxPreview() = PreviewItems.ThreeLinesListItemPreview(trailingContent = PreviewItems.checkbox())
|
||||
|
||||
@Preview(name = "List item (2 lines) - Trailing Checkbox", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemTwoLinesTrailingCheckBoxPreview() = PreviewItems.TwoLinesListItemPreview(trailingContent = PreviewItems.checkbox())
|
||||
|
||||
@Preview(name = "List item (1 line) - Trailing Checkbox", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemSingleLineTrailingCheckBoxPreview() = PreviewItems.OneLineListItemPreview(trailingContent = PreviewItems.checkbox())
|
||||
// endregion
|
||||
|
||||
// region: Trailing RadioButton
|
||||
@Preview(name = "List item (3 lines) - Trailing RadioButton", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemThreeLinesTrailingRadioButtonPreview() = PreviewItems.ThreeLinesListItemPreview(trailingContent = PreviewItems.radioButton())
|
||||
|
||||
@Preview(name = "List item (2 lines) - Trailing RadioButton", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemTwoLinesTrailingRadioButtonPreview() = PreviewItems.TwoLinesListItemPreview(trailingContent = PreviewItems.radioButton())
|
||||
|
||||
@Preview(name = "List item (1 line) - Trailing RadioButton", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemSingleLineTrailingRadioButtonPreview() = PreviewItems.OneLineListItemPreview(trailingContent = PreviewItems.radioButton())
|
||||
// endregion
|
||||
|
||||
// region: Trailing Switch
|
||||
@Preview(name = "List item (3 lines) - Trailing Switch", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemThreeLinesTrailingSwitchPreview() = PreviewItems.ThreeLinesListItemPreview(trailingContent = PreviewItems.switch())
|
||||
|
||||
@Preview(name = "List item (2 lines) - Trailing Switch", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemTwoLinesTrailingSwitchPreview() = PreviewItems.TwoLinesListItemPreview(trailingContent = PreviewItems.switch())
|
||||
|
||||
@Preview(name = "List item (1 line) - Trailing Switch", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemSingleLineTrailingSwitchPreview() = PreviewItems.OneLineListItemPreview(trailingContent = PreviewItems.switch())
|
||||
// endregion
|
||||
|
||||
// region: Trailing Icon
|
||||
@Preview(name = "List item (3 lines) - Trailing Icon", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemThreeLinesTrailingIconPreview() = PreviewItems.ThreeLinesListItemPreview(trailingContent = PreviewItems.icon())
|
||||
|
||||
@Preview(name = "List item (2 lines) - Trailing Icon", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemTwoLinesTrailingIconPreview() = PreviewItems.TwoLinesListItemPreview(trailingContent = PreviewItems.icon())
|
||||
|
||||
@Preview(name = "List item (1 line) - Trailing Icon", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemSingleLineTrailingIconPreview() = PreviewItems.OneLineListItemPreview(trailingContent = PreviewItems.icon())
|
||||
// endregion
|
||||
|
||||
// region: Leading Checkbox
|
||||
@Preview(name = "List item (3 lines) - Leading Checkbox", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemThreeLinesLeadingCheckboxPreview() = PreviewItems.ThreeLinesListItemPreview(leadingContent = PreviewItems.checkbox())
|
||||
|
||||
@Preview(name = "List item (2 lines) - Leading Checkbox", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemTwoLinesLeadingCheckboxPreview() = PreviewItems.TwoLinesListItemPreview(leadingContent = PreviewItems.checkbox())
|
||||
|
||||
@Preview(name = "List item (1 line) - Leading Checkbox", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemSingleLineLeadingCheckboxPreview() = PreviewItems.OneLineListItemPreview(leadingContent = PreviewItems.checkbox())
|
||||
// endregion
|
||||
|
||||
// region: Leading RadioButton
|
||||
@Preview(name = "List item (3 lines) - Leading RadioButton", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemThreeLinesLeadingRadioButtonPreview() = PreviewItems.ThreeLinesListItemPreview(leadingContent = PreviewItems.radioButton())
|
||||
|
||||
@Preview(name = "List item (2 lines) - Leading RadioButton", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemTwoLinesLeadingRadioButtonPreview() = PreviewItems.TwoLinesListItemPreview(leadingContent = PreviewItems.radioButton())
|
||||
|
||||
@Preview(name = "List item (1 line) - Leading RadioButton", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemSingleLineLeadingRadioButtonPreview() = PreviewItems.OneLineListItemPreview(leadingContent = PreviewItems.radioButton())
|
||||
// endregion
|
||||
|
||||
// region: Leading Switch
|
||||
@Preview(name = "List item (3 lines) - Leading Switch", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemThreeLinesLeadingSwitchPreview() = PreviewItems.ThreeLinesListItemPreview(leadingContent = PreviewItems.switch())
|
||||
|
||||
@Preview(name = "List item (2 lines) - Leading Switch", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemTwoLinesLeadingSwitchPreview() = PreviewItems.TwoLinesListItemPreview(leadingContent = PreviewItems.switch())
|
||||
|
||||
@Preview(name = "List item (1 line) - Leading Switch", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemSingleLineLeadingSwitchPreview() = PreviewItems.OneLineListItemPreview(leadingContent = PreviewItems.switch())
|
||||
// endregion
|
||||
|
||||
// region: Leading Icon
|
||||
@Preview(name = "List item (3 lines) - Leading Icon", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemThreeLinesLeadingIconPreview() = PreviewItems.ThreeLinesListItemPreview(leadingContent = PreviewItems.icon())
|
||||
|
||||
@Preview(name = "List item (2 lines) - Leading Icon", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemTwoLinesLeadingIconPreview() = PreviewItems.TwoLinesListItemPreview(leadingContent = PreviewItems.icon())
|
||||
|
||||
@Preview(name = "List item (1 line) - Leading Icon", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemSingleLineLeadingIconPreview() = PreviewItems.OneLineListItemPreview(leadingContent = PreviewItems.icon())
|
||||
// endregion
|
||||
|
||||
// region: Both Icons
|
||||
@Preview(name = "List item (3 lines) - Both Icons", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemThreeLinesBothIconsPreview() = PreviewItems.ThreeLinesListItemPreview(
|
||||
leadingContent = PreviewItems.icon(),
|
||||
trailingContent = PreviewItems.icon()
|
||||
)
|
||||
|
||||
@Preview(name = "List item (2 lines) - Both Icons", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemTwoLinesBothIconsPreview() = PreviewItems.TwoLinesListItemPreview(
|
||||
leadingContent = PreviewItems.icon(),
|
||||
trailingContent = PreviewItems.icon()
|
||||
)
|
||||
|
||||
@Preview(name = "List item (1 line) - Both Icons", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemSingleLineBothIconsPreview() = PreviewItems.OneLineListItemPreview(
|
||||
leadingContent = PreviewItems.icon(),
|
||||
trailingContent = PreviewItems.icon()
|
||||
)
|
||||
// endregion
|
||||
|
||||
// region: Primary action
|
||||
@Preview(name = "List item - Primary action & Icon", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemPrimaryActionWithIconPreview() = PreviewItems.OneLineListItemPreview(
|
||||
style = ListItemStyle.Primary,
|
||||
leadingContent = PreviewItems.icon(),
|
||||
)
|
||||
// endregion
|
||||
|
||||
// region: Error state
|
||||
@Preview(name = "List item - Error", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemErrorPreview() = PreviewItems.OneLineListItemPreview(style = ListItemStyle.Destructive)
|
||||
|
||||
@Preview(name = "List item - Error & Icon", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemErrorWithIconPreview() = PreviewItems.OneLineListItemPreview(
|
||||
style = ListItemStyle.Destructive,
|
||||
leadingContent = PreviewItems.icon(),
|
||||
)
|
||||
// endregion
|
||||
|
||||
// region: Disabled state
|
||||
@Preview(name = "List item - Disabled", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemDisabledPreview() = PreviewItems.OneLineListItemPreview(enabled = false)
|
||||
|
||||
@Preview(name = "List item - Disabled & Icon", group = PreviewGroup.ListItems)
|
||||
@Composable
|
||||
internal fun ListItemDisabledWithIconPreview() = PreviewItems.OneLineListItemPreview(
|
||||
enabled = false,
|
||||
leadingContent = PreviewItems.icon(),
|
||||
)
|
||||
// endregion
|
||||
|
||||
@Suppress("ModifierMissing")
|
||||
private object PreviewItems {
|
||||
|
||||
@Composable
|
||||
fun ThreeLinesListItemPreview(
|
||||
modifier: Modifier = Modifier,
|
||||
leadingContent: @Composable (() -> Unit)? = null,
|
||||
trailingContent: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
ElementThemedPreview {
|
||||
ListItem(
|
||||
headlineContent = PreviewItems.headline(),
|
||||
supportingContent = PreviewItems.text(),
|
||||
leadingContent = leadingContent,
|
||||
trailingContent = trailingContent,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TwoLinesListItemPreview(
|
||||
modifier: Modifier = Modifier,
|
||||
leadingContent: @Composable (() -> Unit)? = null,
|
||||
trailingContent: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
ElementThemedPreview {
|
||||
ListItem(
|
||||
headlineContent = PreviewItems.headline(),
|
||||
supportingContent = PreviewItems.textSingleLine(),
|
||||
leadingContent = leadingContent,
|
||||
trailingContent = trailingContent,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OneLineListItemPreview(
|
||||
modifier: Modifier = Modifier,
|
||||
leadingContent: @Composable (() -> Unit)? = null,
|
||||
trailingContent: @Composable (() -> Unit)? = null,
|
||||
style: ListItemStyle = ListItemStyle.Default,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
ElementThemedPreview {
|
||||
ListItem(
|
||||
headlineContent = PreviewItems.headline(),
|
||||
leadingContent = leadingContent,
|
||||
trailingContent = trailingContent,
|
||||
enabled = enabled,
|
||||
style = style,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun headline() = @Composable {
|
||||
Text("List item")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun text() = @Composable {
|
||||
Text("Supporting line text lorem ipsum dolor sit amet, consectetur.")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun textSingleLine() = @Composable {
|
||||
Text("Supporting line text lorem ipsum dolor sit amet, consectetur.", overflow = TextOverflow.Ellipsis, maxLines = 1)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun checkbox() = @Composable {
|
||||
var checked by remember { mutableStateOf(false) }
|
||||
Checkbox(checked = checked, onCheckedChange = { checked = !checked })
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun radioButton() = @Composable {
|
||||
var checked by remember { mutableStateOf(false) }
|
||||
RadioButton(selected = checked, onClick = { checked = !checked })
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun switch() = @Composable {
|
||||
var checked by remember { mutableStateOf(false) }
|
||||
Switch(checked = checked, onCheckedChange = { checked = !checked })
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun icon() = @Composable {
|
||||
Icon(imageVector = Icons.Outlined.Share, contentDescription = null)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* 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.element.android.libraries.designsystem.theme.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
|
||||
// Designs: https://www.figma.com/file/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?type=design&node-id=425%3A24208&mode=design&t=G5hCfkLB6GgXDuWe-1
|
||||
|
||||
/**
|
||||
* List section header.
|
||||
* @param title The title of the section.
|
||||
* @param modifier The modifier to be applied to the section.
|
||||
* @param hasDivider Whether to show a divider above the section or not. Default is `true`.
|
||||
* @param description A description for the section. It's empty by default.
|
||||
*/
|
||||
@Composable
|
||||
fun ListSectionHeader(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
hasDivider: Boolean = true,
|
||||
description: @Composable () -> Unit = {},
|
||||
) {
|
||||
Column(modifier.fillMaxWidth()) {
|
||||
if (hasDivider) {
|
||||
HorizontalDivider(modifier = Modifier.padding(top = 16.dp))
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides ElementTheme.typography.fontBodySmRegular,
|
||||
LocalContentColor provides ElementTheme.colors.textSecondary,
|
||||
) {
|
||||
description()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List supporting text item. Used to display an explanation in the list with a pre-formatted style.
|
||||
* @param text The text to display.
|
||||
* @param modifier The modifier to be applied to the text.
|
||||
* @param contentPadding The padding to apply to the text. Default is [ListSupportingTextDefaults.Padding.Default].
|
||||
*/
|
||||
@Composable
|
||||
fun ListSupportingText(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
contentPadding: ListSupportingTextDefaults.Padding = ListSupportingTextDefaults.Padding.Default,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
modifier = modifier.padding(contentPadding.paddingValues()),
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* List supporting text item. Used to display an explanation in the list with a pre-formatted style.
|
||||
* @param annotatedString The annotated string to display.
|
||||
* @param modifier The modifier to be applied to the text.
|
||||
* @param contentPadding The padding to apply to the text. Default is [ListSupportingTextDefaults.Padding.Default].
|
||||
*/
|
||||
@Composable
|
||||
fun ListSupportingText(
|
||||
annotatedString: AnnotatedString,
|
||||
modifier: Modifier = Modifier,
|
||||
contentPadding: ListSupportingTextDefaults.Padding = ListSupportingTextDefaults.Padding.Default,
|
||||
) {
|
||||
Text(
|
||||
text = annotatedString,
|
||||
modifier = modifier.padding(contentPadding.paddingValues()),
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
}
|
||||
|
||||
object ListSupportingTextDefaults {
|
||||
|
||||
/** Specifies the padding to use for the supporting text. */
|
||||
sealed interface Padding {
|
||||
/** No padding. */
|
||||
object None : Padding
|
||||
/** Default padding, it will align fine with a [ListItem] with no leading content. */
|
||||
object Default : Padding
|
||||
/** It will align to a [ListItem] with an [Icon] or [Checkbox] as leading content. */
|
||||
object SmallLeadingContent : Padding
|
||||
/** It will align to with a [ListItem] with a [Switch] as leading content. */
|
||||
object LargeLeadingContent : Padding
|
||||
/** It will align to with a [ListItem] with a custom start [padding]. */
|
||||
data class Custom(val padding: Dp) : Padding
|
||||
|
||||
private fun startPadding(): Dp = when (this) {
|
||||
None -> 0.dp
|
||||
Default -> 16.dp
|
||||
SmallLeadingContent -> 56.dp
|
||||
LargeLeadingContent -> 84.dp
|
||||
is Custom -> padding
|
||||
}
|
||||
|
||||
private fun endPadding(): Dp = when (this) {
|
||||
None -> 0.dp
|
||||
else -> 24.dp
|
||||
}
|
||||
|
||||
private fun bottomPadding(): Dp = when (this) {
|
||||
None -> 0.dp
|
||||
else -> 12.dp
|
||||
}
|
||||
|
||||
fun paddingValues() = PaddingValues(
|
||||
top = 0.dp,
|
||||
bottom = bottomPadding(),
|
||||
start = startPadding(),
|
||||
end = endPadding()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region: List header previews
|
||||
|
||||
@Preview(group = PreviewGroup.ListSections, name = "List section header")
|
||||
@Composable
|
||||
internal fun ListSectionHeaderPreview() {
|
||||
ElementThemedPreview {
|
||||
ListSectionHeader(
|
||||
title = "List section",
|
||||
hasDivider = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.ListSections, name = "List section header with divider")
|
||||
@Composable
|
||||
internal fun ListSectionHeaderWithDividerPreview() {
|
||||
ElementThemedPreview {
|
||||
ListSectionHeader(
|
||||
title = "List section",
|
||||
hasDivider = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.ListSections, name = "List section header with description")
|
||||
@Composable
|
||||
internal fun ListSectionHeaderWithDescriptionPreview() {
|
||||
ElementThemedPreview {
|
||||
ListSectionHeader(
|
||||
title = "List section",
|
||||
description = {
|
||||
ListSupportingText(
|
||||
text = "Supporting line text lorem ipsum dolor sit amet, consectetur. Read more",
|
||||
contentPadding = ListSupportingTextDefaults.Padding.None,
|
||||
)
|
||||
},
|
||||
hasDivider = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.ListSections, name = "List section header with description and divider")
|
||||
@Composable
|
||||
internal fun ListSectionHeaderWithDescriptionAndDividerPreview() {
|
||||
ElementThemedPreview {
|
||||
ListSectionHeader(
|
||||
title = "List section",
|
||||
description = {
|
||||
ListSupportingText(
|
||||
text = "Supporting line text lorem ipsum dolor sit amet, consectetur. Read more",
|
||||
contentPadding = ListSupportingTextDefaults.Padding.None,
|
||||
)
|
||||
},
|
||||
hasDivider = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region: List supporting text previews
|
||||
|
||||
@Preview(group = PreviewGroup.ListSections, name = "List supporting text - no padding")
|
||||
@Composable
|
||||
internal fun ListSupportingTextNoPaddingPreview() {
|
||||
ElementThemedPreview {
|
||||
ListSupportingText(
|
||||
text = "Supporting line text lorem ipsum dolor sit amet, consectetur. Read more",
|
||||
contentPadding = ListSupportingTextDefaults.Padding.None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.ListSections, name = "List supporting text - default padding")
|
||||
@Composable
|
||||
internal fun ListSupportingTextDefaultPaddingPreview() {
|
||||
ElementThemedPreview {
|
||||
Column {
|
||||
ListItem(headlineContent = { Text("A title") })
|
||||
ListSupportingText(
|
||||
text = "Supporting line text lorem ipsum dolor sit amet, consectetur. Read more",
|
||||
contentPadding = ListSupportingTextDefaults.Padding.Default,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.ListSections, name = "List supporting text - small padding")
|
||||
@Composable
|
||||
internal fun ListSupportingTextSmallPaddingPreview() {
|
||||
ElementThemedPreview {
|
||||
Column {
|
||||
ListItem(headlineContent = { Text("A title") }, leadingContent = { Icon(Icons.Default.Share, null) })
|
||||
ListSupportingText(
|
||||
text = "Supporting line text lorem ipsum dolor sit amet, consectetur. Read more",
|
||||
contentPadding = ListSupportingTextDefaults.Padding.SmallLeadingContent,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.ListSections, name = "List supporting text - large padding")
|
||||
@Composable
|
||||
internal fun ListSupportingTextLargePaddingPreview() {
|
||||
ElementThemedPreview {
|
||||
Column {
|
||||
ListItem(headlineContent = { Text("A title") }, leadingContent = { Switch(checked = true, onCheckedChange = null) })
|
||||
ListSupportingText(
|
||||
text = "Supporting line text lorem ipsum dolor sit amet, consectetur. Read more",
|
||||
contentPadding = ListSupportingTextDefaults.Padding.LargeLeadingContent,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.ListSections, name = "List supporting text - custom padding")
|
||||
@Composable
|
||||
internal fun ListSupportingTextCustomPaddingPreview() {
|
||||
ElementThemedPreview {
|
||||
Column {
|
||||
ListItem(headlineContent = { Text("A title") })
|
||||
ListSupportingText(
|
||||
text = "Supporting line text lorem ipsum dolor sit amet, consectetur. Read more",
|
||||
contentPadding = ListSupportingTextDefaults.Padding.Custom(24.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user