[Compound] Implement Snackbars based on designs (#1054)
* Make `InternalButton` internal instead of private so it can be customised. Also, change the `ButtonColors.contentColor` for text buttons to `LocalContentColor.current` by default. * Add temporary color for Snackbar action label * Implement `Snackbar` component based on Compound * Propagate changes to all other components * Use right Preview annotation config * Move `ButtonVisuals` to their own file * Update screenshots * Make previews internal * Update screenshots * Set a custom token for contentColor in AppBars * Change 'Label' to 'Action' in the previews * Add changelog * Update screenshots --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
committed by
GitHub
parent
086e5bbd91
commit
e248920909
1
changelog.d/1054.misc
Normal file
1
changelog.d/1054.misc
Normal file
@@ -0,0 +1 @@
|
||||
Compound: implement Snackbar component.
|
||||
@@ -195,7 +195,7 @@ private fun AnalyticsOptInFooter(
|
||||
)
|
||||
TextButton(
|
||||
text = stringResource(id = CommonStrings.action_not_now),
|
||||
buttonSize = ButtonSize.Medium,
|
||||
size = ButtonSize.Medium,
|
||||
onClick = onTermsDeclined,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
@@ -135,7 +135,7 @@ internal fun DefaultInviteSummaryRow(
|
||||
text = stringResource(CommonStrings.action_decline),
|
||||
onClick = onDeclineClicked,
|
||||
modifier = Modifier.weight(1f),
|
||||
buttonSize = ButtonSize.Medium,
|
||||
size = ButtonSize.Medium,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
@@ -144,7 +144,7 @@ internal fun DefaultInviteSummaryRow(
|
||||
text = stringResource(CommonStrings.action_accept),
|
||||
onClick = onAcceptClicked,
|
||||
modifier = Modifier.weight(1f),
|
||||
buttonSize = ButtonSize.Medium,
|
||||
size = ButtonSize.Medium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -78,6 +77,7 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.utils.LogCompositions
|
||||
import io.element.android.libraries.designsystem.utils.SnackbarHost
|
||||
import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
|
||||
@@ -34,9 +34,6 @@ import androidx.compose.material.icons.filled.OpenInNew
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Snackbar
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -64,6 +61,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.utils.SnackbarHost
|
||||
import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.ui.media.MediaRequestData
|
||||
@@ -99,15 +97,7 @@ fun MediaViewerView(
|
||||
eventSink = state.eventSink
|
||||
)
|
||||
},
|
||||
snackbarHost = {
|
||||
SnackbarHost(snackbarHostState) { data ->
|
||||
Snackbar(
|
||||
snackbarData = data,
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
contentColor = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
||||
@@ -24,8 +24,6 @@ import androidx.compose.material.icons.outlined.DeveloperMode
|
||||
import androidx.compose.material.icons.outlined.Help
|
||||
import androidx.compose.material.icons.outlined.InsertChart
|
||||
import androidx.compose.material.icons.outlined.VerifiedUser
|
||||
import androidx.compose.material3.Snackbar
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -41,6 +39,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.preview.LargeHeightPreview
|
||||
import io.element.android.libraries.designsystem.theme.components.Divider
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.utils.SnackbarHost
|
||||
import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.components.MatrixUserProvider
|
||||
@@ -65,13 +64,7 @@ fun PreferencesRootView(
|
||||
modifier = modifier,
|
||||
onBackPressed = onBackPressed,
|
||||
title = stringResource(id = CommonStrings.common_settings),
|
||||
snackbarHost = {
|
||||
SnackbarHost(snackbarHostState) { data ->
|
||||
Snackbar(
|
||||
snackbarData = data,
|
||||
)
|
||||
}
|
||||
}
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) }
|
||||
) {
|
||||
UserPreferences(state.myUser)
|
||||
if (state.showCompleteVerification) {
|
||||
|
||||
@@ -28,8 +28,6 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Snackbar
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -59,6 +57,7 @@ import io.element.android.libraries.designsystem.theme.components.FloatingAction
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.utils.LogCompositions
|
||||
import io.element.android.libraries.designsystem.utils.SnackbarHost
|
||||
import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.designsystem.R as DrawableR
|
||||
@@ -227,13 +226,7 @@ fun RoomListContent(
|
||||
)
|
||||
}
|
||||
},
|
||||
snackbarHost = {
|
||||
SnackbarHost(snackbarHostState) { data ->
|
||||
Snackbar(
|
||||
snackbarData = data,
|
||||
)
|
||||
}
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ internal fun RequestVerificationHeader(
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_continue),
|
||||
buttonSize = ButtonSize.Medium,
|
||||
size = ButtonSize.Medium,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = onVerifyClicked,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.components.button
|
||||
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
|
||||
/**
|
||||
* A sealed class that represents the different visual styles that a button can have.
|
||||
*/
|
||||
sealed interface ButtonVisuals {
|
||||
|
||||
val action: () -> Unit
|
||||
|
||||
/**
|
||||
* Creates a [Button] composable based on the visual state.
|
||||
*/
|
||||
@Composable
|
||||
fun Composable()
|
||||
|
||||
data class Text(val text: String, override val action: () -> Unit) : ButtonVisuals {
|
||||
@Composable
|
||||
override fun Composable() {
|
||||
TextButton(text = text, onClick = action)
|
||||
}
|
||||
}
|
||||
data class Icon(val iconSource: IconSource, override val action: () -> Unit) : ButtonVisuals {
|
||||
@Composable
|
||||
override fun Composable() {
|
||||
IconButton(onClick = action) {
|
||||
Icon(iconSource.getPainter(), iconSource.contentDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ object PreviewGroup {
|
||||
const val Preferences = "Preferences"
|
||||
const val Progress = "Progress Indicators"
|
||||
const val Search = "Search views"
|
||||
const val Snackbars = "Snackbars"
|
||||
const val Sliders = "Sliders"
|
||||
const val Text = "Text"
|
||||
const val TextFields = "TextFields"
|
||||
|
||||
@@ -78,7 +78,7 @@ private fun ContentToPreview() {
|
||||
WithRulers(xRulersOffset = 20.dp, yRulersOffset = 15.dp) {
|
||||
OutlinedButton(
|
||||
text = "A Button with rulers on it!",
|
||||
buttonSize = ButtonSize.Medium,
|
||||
size = ButtonSize.Medium,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -72,19 +72,19 @@ internal fun SimpleAlertDialogContent(
|
||||
// Having this 3rd action is discouraged, see https://m3.material.io/components/dialogs/guidelines#e13b68f5-e367-4275-ad6f-c552ee8e358f
|
||||
TextButton(
|
||||
text = thirdButtonText,
|
||||
buttonSize = ButtonSize.Medium,
|
||||
size = ButtonSize.Medium,
|
||||
onClick = onThirdButtonClicked,
|
||||
)
|
||||
}
|
||||
TextButton(
|
||||
text = cancelText,
|
||||
buttonSize = ButtonSize.Medium,
|
||||
size = ButtonSize.Medium,
|
||||
onClick = onCancelClicked,
|
||||
)
|
||||
if (submitText != null) {
|
||||
Button(
|
||||
text = submitText,
|
||||
buttonSize = ButtonSize.Medium,
|
||||
size = ButtonSize.Medium,
|
||||
onClick = onSubmitClicked,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import androidx.compose.foundation.progressSemantics
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Share
|
||||
import androidx.compose.material3.ButtonColors
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -41,6 +42,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.graphics.isSpecified
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
@@ -60,10 +62,19 @@ fun Button(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
buttonSize: ButtonSize = ButtonSize.Large,
|
||||
size: ButtonSize = ButtonSize.Large,
|
||||
showProgress: Boolean = false,
|
||||
leadingIcon: IconSource? = null,
|
||||
) = ButtonInternal(text, onClick, ButtonStyle.Filled, modifier, enabled, buttonSize, showProgress, leadingIcon)
|
||||
) = ButtonInternal(
|
||||
text = text,
|
||||
onClick = onClick,
|
||||
style = ButtonStyle.Filled,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
size = size,
|
||||
showProgress = showProgress,
|
||||
leadingIcon = leadingIcon
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun OutlinedButton(
|
||||
@@ -71,10 +82,19 @@ fun OutlinedButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
buttonSize: ButtonSize = ButtonSize.Large,
|
||||
size: ButtonSize = ButtonSize.Large,
|
||||
showProgress: Boolean = false,
|
||||
leadingIcon: IconSource? = null,
|
||||
) = ButtonInternal(text, onClick, ButtonStyle.Outlined, modifier, enabled, buttonSize, showProgress, leadingIcon)
|
||||
) = ButtonInternal(
|
||||
text = text,
|
||||
onClick = onClick,
|
||||
style = ButtonStyle.Outlined,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
size = size,
|
||||
showProgress = showProgress,
|
||||
leadingIcon = leadingIcon
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun TextButton(
|
||||
@@ -82,17 +102,27 @@ fun TextButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
buttonSize: ButtonSize = ButtonSize.Large,
|
||||
size: ButtonSize = ButtonSize.Large,
|
||||
showProgress: Boolean = false,
|
||||
leadingIcon: IconSource? = null,
|
||||
) = ButtonInternal(text, onClick, ButtonStyle.Text, modifier, enabled, buttonSize, showProgress, leadingIcon)
|
||||
) = ButtonInternal(
|
||||
text = text,
|
||||
onClick = onClick,
|
||||
style = ButtonStyle.Text,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
size = size,
|
||||
showProgress = showProgress,
|
||||
leadingIcon = leadingIcon
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun ButtonInternal(
|
||||
internal fun ButtonInternal(
|
||||
text: String,
|
||||
onClick: () -> Unit,
|
||||
style: ButtonStyle,
|
||||
modifier: Modifier = Modifier,
|
||||
colors: ButtonColors = style.getColors(),
|
||||
enabled: Boolean = true,
|
||||
size: ButtonSize = ButtonSize.Large,
|
||||
showProgress: Boolean = false,
|
||||
@@ -123,21 +153,6 @@ private fun ButtonInternal(
|
||||
ButtonStyle.Text -> RectangleShape
|
||||
}
|
||||
|
||||
val colors = when (style) {
|
||||
ButtonStyle.Filled -> ButtonDefaults.buttonColors(
|
||||
containerColor = ElementTheme.materialColors.primary,
|
||||
contentColor = ElementTheme.materialColors.onPrimary,
|
||||
disabledContainerColor = ElementTheme.colors.bgActionPrimaryDisabled,
|
||||
disabledContentColor = ElementTheme.colors.textOnSolidPrimary
|
||||
)
|
||||
ButtonStyle.Outlined, ButtonStyle.Text -> ButtonDefaults.buttonColors(
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = ElementTheme.materialColors.primary,
|
||||
disabledContainerColor = Color.Transparent,
|
||||
disabledContentColor = ElementTheme.colors.textDisabled,
|
||||
)
|
||||
}
|
||||
|
||||
val border = when (style) {
|
||||
ButtonStyle.Filled, ButtonStyle.Text -> null
|
||||
ButtonStyle.Outlined -> BorderStroke(
|
||||
@@ -202,8 +217,10 @@ private fun ButtonInternal(
|
||||
}
|
||||
|
||||
sealed interface IconSource {
|
||||
data class Resource(val id: Int) : IconSource
|
||||
data class Vector(val vector: ImageVector) : IconSource
|
||||
val contentDescription: String?
|
||||
|
||||
data class Resource(val id: Int, override val contentDescription: String? = null) : IconSource
|
||||
data class Vector(val vector: ImageVector, override val contentDescription: String? = null) : IconSource
|
||||
|
||||
@Composable
|
||||
fun getPainter(): Painter = when (this) {
|
||||
@@ -216,16 +233,38 @@ enum class ButtonSize {
|
||||
Medium, Large
|
||||
}
|
||||
|
||||
private enum class ButtonStyle {
|
||||
Filled, Outlined, Text
|
||||
internal enum class ButtonStyle {
|
||||
Filled, Outlined, Text;
|
||||
|
||||
@Composable
|
||||
fun getColors(): ButtonColors = when (this) {
|
||||
Filled -> ButtonDefaults.buttonColors(
|
||||
containerColor = ElementTheme.materialColors.primary,
|
||||
contentColor = ElementTheme.materialColors.onPrimary,
|
||||
disabledContainerColor = ElementTheme.colors.bgActionPrimaryDisabled,
|
||||
disabledContentColor = ElementTheme.colors.textOnSolidPrimary
|
||||
)
|
||||
Outlined -> ButtonDefaults.buttonColors(
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = ElementTheme.materialColors.primary,
|
||||
disabledContainerColor = Color.Transparent,
|
||||
disabledContentColor = ElementTheme.colors.textDisabled,
|
||||
)
|
||||
Text -> ButtonDefaults.buttonColors(
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = if (LocalContentColor.current.isSpecified) LocalContentColor.current else ElementTheme.materialColors.primary,
|
||||
disabledContainerColor = Color.Transparent,
|
||||
disabledContentColor = ElementTheme.colors.textDisabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.Buttons)
|
||||
@Composable
|
||||
internal fun FilledButtonMediumPreview() {
|
||||
ButtonCombinationPreview(
|
||||
buttonStyle = ButtonStyle.Filled,
|
||||
buttonSize = ButtonSize.Medium,
|
||||
style = ButtonStyle.Filled,
|
||||
size = ButtonSize.Medium,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -233,8 +272,8 @@ internal fun FilledButtonMediumPreview() {
|
||||
@Composable
|
||||
internal fun FilledButtonLargePreview() {
|
||||
ButtonCombinationPreview(
|
||||
buttonStyle = ButtonStyle.Filled,
|
||||
buttonSize = ButtonSize.Large,
|
||||
style = ButtonStyle.Filled,
|
||||
size = ButtonSize.Large,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -242,8 +281,8 @@ internal fun FilledButtonLargePreview() {
|
||||
@Composable
|
||||
internal fun OutlinedButtonMediumPreview() {
|
||||
ButtonCombinationPreview(
|
||||
buttonStyle = ButtonStyle.Outlined,
|
||||
buttonSize = ButtonSize.Medium,
|
||||
style = ButtonStyle.Outlined,
|
||||
size = ButtonSize.Medium,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -251,8 +290,8 @@ internal fun OutlinedButtonMediumPreview() {
|
||||
@Composable
|
||||
internal fun OutlinedButtonLargePreview() {
|
||||
ButtonCombinationPreview(
|
||||
buttonStyle = ButtonStyle.Outlined,
|
||||
buttonSize = ButtonSize.Large,
|
||||
style = ButtonStyle.Outlined,
|
||||
size = ButtonSize.Large,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -260,8 +299,8 @@ internal fun OutlinedButtonLargePreview() {
|
||||
@Composable
|
||||
internal fun TextButtonMediumPreview() {
|
||||
ButtonCombinationPreview(
|
||||
buttonStyle = ButtonStyle.Text,
|
||||
buttonSize = ButtonSize.Medium,
|
||||
style = ButtonStyle.Text,
|
||||
size = ButtonSize.Medium,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -269,15 +308,15 @@ internal fun TextButtonMediumPreview() {
|
||||
@Composable
|
||||
internal fun TextButtonLargePreview() {
|
||||
ButtonCombinationPreview(
|
||||
buttonStyle = ButtonStyle.Text,
|
||||
buttonSize = ButtonSize.Large,
|
||||
style = ButtonStyle.Text,
|
||||
size = ButtonSize.Large,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ButtonCombinationPreview(
|
||||
buttonStyle: ButtonStyle,
|
||||
buttonSize: ButtonSize,
|
||||
style: ButtonStyle,
|
||||
size: ButtonSize,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ElementThemedPreview {
|
||||
@@ -290,24 +329,24 @@ private fun ButtonCombinationPreview(
|
||||
// Normal
|
||||
ButtonRowPreview(
|
||||
modifier = Modifier.then(modifier),
|
||||
buttonStyle = buttonStyle,
|
||||
buttonSize = buttonSize,
|
||||
style = style,
|
||||
size = size,
|
||||
)
|
||||
|
||||
// With icon
|
||||
ButtonRowPreview(
|
||||
modifier = Modifier.then(modifier),
|
||||
leadingIcon = IconSource.Vector(Icons.Outlined.Share),
|
||||
buttonStyle = buttonStyle,
|
||||
buttonSize = buttonSize,
|
||||
style = style,
|
||||
size = size,
|
||||
)
|
||||
|
||||
// With progress
|
||||
ButtonRowPreview(
|
||||
modifier = Modifier.then(modifier),
|
||||
showProgress = true,
|
||||
buttonStyle = buttonStyle,
|
||||
buttonSize = buttonSize,
|
||||
style = style,
|
||||
size = size,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -315,8 +354,8 @@ private fun ButtonCombinationPreview(
|
||||
|
||||
@Composable
|
||||
private fun ButtonRowPreview(
|
||||
buttonStyle: ButtonStyle,
|
||||
buttonSize: ButtonSize,
|
||||
style: ButtonStyle,
|
||||
size: ButtonSize,
|
||||
modifier: Modifier = Modifier,
|
||||
leadingIcon: IconSource? = null,
|
||||
showProgress: Boolean = false,
|
||||
@@ -326,8 +365,8 @@ private fun ButtonRowPreview(
|
||||
text = "A button",
|
||||
showProgress = showProgress,
|
||||
onClick = {},
|
||||
style = buttonStyle,
|
||||
size = buttonSize,
|
||||
style = style,
|
||||
size = size,
|
||||
leadingIcon = leadingIcon,
|
||||
modifier = Modifier.then(modifier),
|
||||
)
|
||||
@@ -336,8 +375,8 @@ private fun ButtonRowPreview(
|
||||
showProgress = showProgress,
|
||||
enabled = false,
|
||||
onClick = {},
|
||||
style = buttonStyle,
|
||||
size = buttonSize,
|
||||
style = style,
|
||||
size = size,
|
||||
leadingIcon = leadingIcon,
|
||||
modifier = Modifier.then(modifier),
|
||||
)
|
||||
|
||||
@@ -18,15 +18,21 @@ package io.element.android.libraries.designsystem.theme.components
|
||||
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.TopAppBarColors
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -43,7 +49,11 @@ fun MediumTopAppBar(
|
||||
title = title,
|
||||
modifier = modifier,
|
||||
navigationIcon = navigationIcon,
|
||||
actions = actions,
|
||||
actions = {
|
||||
CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.textActionPrimary) {
|
||||
actions()
|
||||
}
|
||||
},
|
||||
windowInsets = windowInsets,
|
||||
colors = colors,
|
||||
scrollBehavior = scrollBehavior,
|
||||
@@ -58,5 +68,14 @@ internal fun MediumTopAppBarPreview() =
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ContentToPreview() {
|
||||
MediumTopAppBar(title = { Text(text = "Title") })
|
||||
MediumTopAppBar(
|
||||
title = { Text(text = "Title") },
|
||||
navigationIcon = { BackButton(onClick = {}) },
|
||||
actions = {
|
||||
TextButton(text = "Action", onClick = {})
|
||||
IconButton(onClick = {}) {
|
||||
Icon(imageVector = Icons.Default.Share, contentDescription = null)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.SnackbarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.components.button.ButtonVisuals
|
||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import io.element.android.libraries.theme.SnackBarLabelColorDark
|
||||
import io.element.android.libraries.theme.SnackBarLabelColorLight
|
||||
|
||||
@Composable
|
||||
fun Snackbar(
|
||||
message: String,
|
||||
modifier: Modifier = Modifier,
|
||||
action: ButtonVisuals? = null,
|
||||
dismissAction: ButtonVisuals? = null,
|
||||
actionOnNewLine: Boolean = false,
|
||||
shape: Shape = RoundedCornerShape(8.dp),
|
||||
containerColor: Color = SnackbarDefaults.color,
|
||||
contentColor: Color = ElementTheme.materialColors.inverseOnSurface,
|
||||
actionContentColor: Color = actionContentColor(),
|
||||
dismissActionContentColor: Color = SnackbarDefaults.dismissActionContentColor,
|
||||
) {
|
||||
Snackbar(
|
||||
modifier = modifier,
|
||||
action = action?.let { @Composable { it.Composable() } },
|
||||
dismissAction = dismissAction?.let { @Composable { it.Composable() } },
|
||||
actionOnNewLine = actionOnNewLine,
|
||||
shape = shape,
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
actionContentColor = actionContentColor,
|
||||
dismissActionContentColor = dismissActionContentColor,
|
||||
content = { Text(text = message) },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Snackbar(
|
||||
modifier: Modifier = Modifier,
|
||||
action: @Composable (() -> Unit)? = null,
|
||||
dismissAction: @Composable (() -> Unit)? = null,
|
||||
actionOnNewLine: Boolean = false,
|
||||
shape: Shape = RoundedCornerShape(8.dp),
|
||||
containerColor: Color = SnackbarDefaults.color,
|
||||
contentColor: Color = ElementTheme.materialColors.inverseOnSurface,
|
||||
actionContentColor: Color = actionContentColor(),
|
||||
dismissActionContentColor: Color = SnackbarDefaults.dismissActionContentColor,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
androidx.compose.material3.Snackbar(
|
||||
modifier = modifier,
|
||||
action = action,
|
||||
dismissAction = dismissAction,
|
||||
actionOnNewLine = actionOnNewLine,
|
||||
shape = shape,
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
actionContentColor = actionContentColor,
|
||||
dismissActionContentColor = dismissActionContentColor,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO this color is temporary, an `inverse` version should be added to the semantic colors instead
|
||||
@Composable
|
||||
private fun actionContentColor(): Color {
|
||||
return if (ElementTheme.isLightTheme) {
|
||||
SnackBarLabelColorLight
|
||||
} else {
|
||||
SnackBarLabelColorDark
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Snackbar", group = PreviewGroup.Snackbars)
|
||||
@Composable
|
||||
internal fun SnackbarPreview() {
|
||||
ElementThemedPreview {
|
||||
Snackbar(message = "Snackbar supporting text")
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Snackbar with action", group = PreviewGroup.Snackbars)
|
||||
@Composable
|
||||
internal fun SnackbarWithActionPreview() {
|
||||
ElementThemedPreview {
|
||||
Snackbar(message = "Snackbar supporting text", action = ButtonVisuals.Text("Action", {}))
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Snackbar with action and close button", group = PreviewGroup.Snackbars)
|
||||
@Composable
|
||||
internal fun SnackbarWithActionAndCloseButtonPreview() {
|
||||
ElementThemedPreview {
|
||||
Snackbar(
|
||||
message = "Snackbar supporting text",
|
||||
action = ButtonVisuals.Text("Action", {}),
|
||||
dismissAction = ButtonVisuals.Icon(IconSource.Vector(Icons.Default.Close), {})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Snackbar with action on new line", group = PreviewGroup.Snackbars)
|
||||
@Composable
|
||||
internal fun SnackbarWithActionOnNewLinePreview() {
|
||||
ElementThemedPreview {
|
||||
Snackbar(message = "Snackbar supporting text", action = ButtonVisuals.Text("Action", {}), actionOnNewLine = true)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Snackbar with action and close button on new line", group = PreviewGroup.Snackbars)
|
||||
@Composable
|
||||
internal fun SnackbarWithActionOnNewLineAndCloseButtonPreview() {
|
||||
ElementThemedPreview {
|
||||
Snackbar(
|
||||
message = "Snackbar supporting text",
|
||||
action = ButtonVisuals.Text("Action", {}),
|
||||
dismissAction = ButtonVisuals.Icon(IconSource.Vector(Icons.Default.Close), {}),
|
||||
actionOnNewLine = true
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -18,15 +18,21 @@ package io.element.android.libraries.designsystem.theme.components
|
||||
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.TopAppBarColors
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -43,7 +49,11 @@ fun TopAppBar(
|
||||
title = title,
|
||||
modifier = modifier,
|
||||
navigationIcon = navigationIcon,
|
||||
actions = actions,
|
||||
actions = {
|
||||
CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.textActionPrimary) {
|
||||
actions()
|
||||
}
|
||||
},
|
||||
windowInsets = windowInsets,
|
||||
colors = colors,
|
||||
scrollBehavior = scrollBehavior,
|
||||
@@ -58,5 +68,14 @@ internal fun TopAppBarPreview() =
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ContentToPreview() {
|
||||
TopAppBar(title = { Text(text = "Title") })
|
||||
TopAppBar(
|
||||
title = { Text(text = "Title") },
|
||||
navigationIcon = { BackButton(onClick = {}) },
|
||||
actions = {
|
||||
TextButton(text = "Action", onClick = {})
|
||||
IconButton(onClick = {}) {
|
||||
Icon(imageVector = Icons.Default.Share, contentDescription = null)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package io.element.android.libraries.designsystem.utils
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -25,7 +27,11 @@ import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.libraries.designsystem.components.button.ButtonVisuals
|
||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||
import io.element.android.libraries.designsystem.theme.components.Snackbar
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@@ -65,6 +71,19 @@ fun SnackbarDispatcher.collectSnackbarMessageAsState(): State<SnackbarMessage?>
|
||||
return snackbarMessage.collectAsState(initial = null)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SnackbarHost(hostState: SnackbarHostState, modifier: Modifier = Modifier) {
|
||||
androidx.compose.material3.SnackbarHost(hostState, modifier) { data ->
|
||||
Snackbar(
|
||||
message = data.visuals.message,
|
||||
action = data.visuals.actionLabel?.let { ButtonVisuals.Text(it, data::performAction) },
|
||||
dismissAction = if (data.visuals.withDismissAction) {
|
||||
ButtonVisuals.Icon(IconSource.Vector(Icons.Default.Close), data::dismiss)
|
||||
} else null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberSnackbarHostState(snackbarMessage: SnackbarMessage?): SnackbarHostState {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package io.element.android.libraries.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import io.element.android.libraries.theme.compound.generated.internal.DarkDesignTokens
|
||||
import io.element.android.libraries.theme.compound.generated.internal.LightDesignTokens
|
||||
|
||||
// =================================================================================================
|
||||
// IMPORTANT!
|
||||
@@ -26,3 +28,6 @@ import androidx.compose.ui.graphics.Color
|
||||
// =================================================================================================
|
||||
|
||||
val LinkColor = Color(0xFF0086E6)
|
||||
|
||||
val SnackBarLabelColorLight = LightDesignTokens.colorGray700
|
||||
val SnackBarLabelColorDark = DarkDesignTokens.colorGray700
|
||||
|
||||
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