Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compound: add BigIcon, BigCheckmark and PageTitle components #2574

Merged
merged 5 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2024 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

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.bigCheckmarkBorderColor
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Surface
import io.element.android.libraries.ui.strings.CommonStrings

/**
* Compound component that displays a big checkmark centered in a rounded square.
*
* @param modifier the modifier to apply to this layout
*/
@Composable
fun BigCheckmark(
modifier: Modifier = Modifier,
) {
Surface(
modifier = modifier.size(120.dp),
shape = RoundedCornerShape(14.dp),
color = ElementTheme.colors.bgCanvasDefault,
border = BorderStroke(1.dp, ElementTheme.colors.bigCheckmarkBorderColor),
shadowElevation = 4.dp,
) {
Box(contentAlignment = Alignment.Center) {
Icon(
modifier = Modifier.size(72.dp),
tint = ElementTheme.colors.iconSuccessPrimary,
imageVector = CompoundIcons.CheckCircleSolid(),
contentDescription = stringResource(CommonStrings.common_success)
)
}
}
}

@PreviewsDayNight
@Composable
internal fun BigCheckmarkPreview() {
ElementPreview {
Box(
modifier = Modifier.padding(10.dp),
contentAlignment = Alignment.Center,
) {
BigCheckmark()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Copyright (c) 2024 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

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CatchingPokemon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.bigIconDefaultBackgroundColor
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.ui.strings.CommonStrings

/**
* Compound component that display a big icon centered in a rounded square.
*/
object BigIcon {
/**
* The style of the [BigIcon].
*/
@Immutable
sealed interface Style {
/**
* The default style.
*
* @param vectorIcon the [ImageVector] to display
* @param contentDescription the content description of the icon, if any. It defaults to `null`
*/
data class Default(val vectorIcon: ImageVector, val contentDescription: String? = null) : Style

/**
* An alert style with a tinted background.
*/
data object Alert : Style

/**
* An alert style with the default background color.
*/
data object AlertSolid : Style

/**
* A success style with a tinted background.
*/
data object Success : Style

/**
* A success style with the default background color.
*/
data object SuccessSolid : Style
}

/**
* Display a [BigIcon].
*
* @param style the style of the icon
* @param modifier the modifier to apply to this layout
*/
@Composable
operator fun invoke(
style: Style,
modifier: Modifier = Modifier,
) {
val backgroundColor = when (style) {
is Style.Default -> ElementTheme.colors.bigIconDefaultBackgroundColor
Style.AlertSolid, Style.SuccessSolid -> ElementTheme.colors.bgCanvasDefault
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if using a transparent color should be preferable in this case. I let you decide.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transparent is probably better, although it kind of contradicts its name. I'm asking in the Compound room to clarify this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just confirmed it with them, it's the other way around: the solid ones should have a solid background (duh), the others should be transparent. I'll change those.

Style.Alert -> ElementTheme.colors.bgCriticalSubtle
Style.Success -> ElementTheme.colors.bgSuccessSubtle
}
val icon = when (style) {
is Style.Default -> style.vectorIcon
Style.Alert, Style.AlertSolid -> CompoundIcons.Error()
Style.Success, Style.SuccessSolid -> CompoundIcons.CheckCircleSolid()
}
val contentDescription = when (style) {
is Style.Default -> style.contentDescription
Style.Alert, Style.AlertSolid -> stringResource(CommonStrings.common_error)
Style.Success, Style.SuccessSolid -> stringResource(CommonStrings.common_success)
}
val iconTint = when (style) {
is Style.Default -> ElementTheme.colors.iconSecondaryAlpha
Style.Alert, Style.AlertSolid -> ElementTheme.colors.iconCriticalPrimary
Style.Success, Style.SuccessSolid -> ElementTheme.colors.iconSuccessPrimary
}
Box(
modifier = modifier
.size(64.dp)
.clip(RoundedCornerShape(14.dp))
.background(backgroundColor),
contentAlignment = Alignment.Center,
) {
Icon(
modifier = Modifier.size(32.dp),
tint = iconTint,
imageVector = icon,
contentDescription = contentDescription
)
}
}
}

@PreviewsDayNight
@Composable
internal fun BigIconPreview() {
ElementPreview {
Row(horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.padding(10.dp)) {
val provider = BigIconStylePreviewProvider()
for (style in provider.values) {
BigIcon(style = style)
}
}
}
}

internal class BigIconStylePreviewProvider : PreviewParameterProvider<BigIcon.Style> {
override val values: Sequence<BigIcon.Style>
get() = sequenceOf(
BigIcon.Style.Default(Icons.Filled.CatchingPokemon),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

BigIcon.Style.Alert,
BigIcon.Style.AlertSolid,
BigIcon.Style.Success,
BigIcon.Style.SuccessSolid
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* Copyright (c) 2024 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

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton

/**
* Compound component that displays a big icon, a title, an optional subtitle and an optional call to action component.
*
* @param title the title to display
* @param iconStyle the style of the [BigIcon] to display
* @param modifier the modifier to apply to this layout
* @param subtitle the optional subtitle to display. It defaults to `null`
* @param callToAction the optional call to action component to display. It defaults to `null`
*/
@Composable
fun PageTitle(
title: AnnotatedString,
iconStyle: BigIcon.Style,
modifier: Modifier = Modifier,
subtitle: AnnotatedString? = null,
callToAction: @Composable (() -> Unit)? = null,
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(bottom = 40.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
BigIcon(style = iconStyle)
Column(
modifier = Modifier.padding(vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = title,
style = ElementTheme.typography.fontHeadingMdBold,
color = ElementTheme.colors.textPrimary,
textAlign = TextAlign.Center,
)

subtitle?.let {
Text(
modifier = Modifier.fillMaxWidth(),
text = it,
style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.colors.textSecondary,
textAlign = TextAlign.Center,
)
}
}
callToAction?.invoke()
}
}

/**
* Compound component that displays a big icon, a title, an optional subtitle and an optional call to action component.
*
* @param title the title to display
* @param iconStyle the style of the [BigIcon] to display
* @param modifier the modifier to apply to this layout
* @param subtitle the optional subtitle to display. It defaults to `null`
* @param callToAction the optional call to action component to display. It defaults to `null`
*/
@Composable
fun PageTitle(
title: String,
iconStyle: BigIcon.Style,
modifier: Modifier = Modifier,
subtitle: String? = null,
callToAction: @Composable (() -> Unit)? = null,
) = PageTitle(
title = AnnotatedString(title),
iconStyle = iconStyle,
modifier = modifier,
subtitle = subtitle?.let { AnnotatedString(it) },
callToAction = callToAction
)

@PreviewsDayNight
@Composable
internal fun TitleWithIconFullPreview(@PreviewParameter(BigIconStylePreviewProvider::class) style: BigIcon.Style) {
ElementPreview {
PageTitle(
modifier = Modifier.padding(top = 24.dp),
title = AnnotatedString("Headline"),
subtitle = AnnotatedString("Description goes here"),
iconStyle = style,
callToAction = {
TextButton(text = "Learn more", onClick = {})
}
)
}
}

@PreviewsDayNight
@Composable
internal fun TitleWithIconMinimalPreview() {
ElementPreview {
PageTitle(
modifier = Modifier.padding(top = 24.dp),
title = "Headline",
iconStyle = BigIcon.Style.Default(CompoundIcons.CheckCircleSolid()),
)
}
}
Loading
Loading