Skip to content

Fiori Horizon List Cards

Design

The Fiori Horizon List Card displays a list of similar cells inside a card view. The List Card can also have a header at the top and/or action buttons at the bottom of the list. The List Card supports both Light mode and Dark mode, determined by the device's settings.

List Cards in Light Mode List Cards in Dark Mode
List Cards on a Large Screen

Development

The Fiori Horizon List Card is implemented entirely using Jetpack Compose, including a Composable FioriHorizonTheme. To understand the fundamentals of Jetpack Compose, refer to the Android Developer's documentation.

The fiori-horizon-cards module provides two UI components for application developers to use out of the box:

  • ListCard: a UI component to display a single List Card.
  • ListCardGrid: a UI component to display a series of List Cards in a vertically scrollable grid that is responsive to different screen sizes.

Usage in an Application

This section describes how to use the List Card components in an application.

Sample Application on a Phone in Light mode Sample Application on a Phone in Dark mode

Sample Android Application on a Large Tablet

1. How to Create a ListCard

ListCard is a Composable function that takes four parameters:

  • A ListCardData that specifies the data to be displayed on the card
  • A Modifier that is used to define the height and width of the card and/or a TestTag needed for Unit Tests. A default listCardModifier is predefined to set the minimum height and width requirements.
  • A ListCardTextColors to customize the default text colors used.
  • A ListCardTextStyles to customize the default text styles used.
@Composable
fun ListCard(
    data: ListCardData,
    modifier: Modifier = listCardModifier,
    textColors: ListCardTextColors = ListCardDefaults.textColors(),
    textStyles: ListCardTextStyles = ListCardDefaults.textStyles()
) {
    val uiState = rememberListCardUiState(data = data)
    ListCardContent(
        darkTheme = isSystemInDarkTheme(),
        uiState = uiState,
        modifier = modifier,
        textColors = textColors,
        textStyles = textStyles
    )
}

An object of the ListCardData has to be passed in from the application in order to create a ListCard.

@Parcelize
data class ListCardData(
    val listCells: @RawValue List<ListCell>,
    val header: String? = null,
    val imageThumbnail: @RawValue ImageThumbnail? = null,
    val menuItems: @RawValue List<MenuItem>? = listOf(),
    val primaryActionButton: @RawValue ActionButton? = null,
    val secondaryActionButton: @RawValue ActionButton? = null,
    val cardStyles: @RawValue CardStyles? = null
) : Parcelable

data class ListCell(
    val title: String,
    val subtitle: String? = null,
    val footnote: String? = null,
    val status: ListCellStatus? = null,
    val imageThumbnail: @RawValue ImageThumbnail? = null,
    val listCellAction: ListCellAction,
    val clickable: Boolean = true,
    val onClick: () -> Unit?
)

Creating a ListCard can be as simple as the following (if there is no need to provide a custom Modifier):

    FioriHorizonTheme(darkTheme = false) {
        val listCardData = remember { generateListCardExample() }
        ListCard(data = listCardData)
    }

You can customize the ListCard's style, including Action Button styles and card background color, by supplying new values in the ListCardData. For example, to change the background color of the ActionButton on the ListCells of a ListCard from the default color to green:

val LC_LISTCELL4 = ListCell(
    title = "Maternity Leave",
    subtitle = "09/10/21 - 01/12/22",
    footnote =  "Leave Request",
    imageThumbnail = ImageThumbnail(
        imageType = ImageType.RESOURCE,
        resId = R.drawable.angular_square,
        imageDesc = "Image Description"
    ),
    listCellAction = ListCellAction(
        actionType = ListCellActionType.TEXT,
        textActionButton = ActionButton("Approve", {}, false, Color.Green, Color.Green)
    ),
    clickable = true,
    onClick = {}
)

val LISTCARDEXAMPLE4 = ListCardData(
    listCells = List(5){ LC_LISTCELL4},
    header = LC_HEADER,
    imageThumbnail = LC_IMAGETHUMBNAIL,
    primaryActionButton = LC_PRIMARYACTIONBUTTON,
    secondaryActionButton = LC_SECONDARYACTIONBUTTON,
    menuItems = LC_MENUITEMSWITHICONS
)

fun generateListCardExample(): ListCardData {
    return LISTCARDEXAMPLE4
}

FioriHorizonTheme(darkTheme = false) {
    val listCardData = remember { generateListCardExample() }
    ListCard(data = listCardData)
}

When this card is drawn on the screen, the Approve buttons will be green.

List Card with default button color List Card with custom button color

2. How to Create a ListCardGrid

To display a series of ListCards on the screen, use a ListCardGrid.

A ListCardGrid is defined as a Composable function in the fiori-horizon-cards module. In order to create a ListCardGrid, an application only needs to know the signatures of this function.

@Composable
fun ListCardGrid(
    cardList: List<ListCardData>,
    screenType: Enum<ScreenType>,
    modifier: Modifier = Modifier
){
    ...
}

An application should pass in a list of ListCardData objects as well as an Enum value for ScreenType. For example:

    FioriHorizonTheme {
        val listCardDataList = remember { generateListCardList() }
        val screenType = getScreenType()
        ListCardGrid(
            cardList = listCardDataList,
            screenType = screenType,
            modifier = Modifier.then(Modifier.wrapContentHeight(Alignment.Top))
        )
    }

Within the fiori-horizon-cards module, a utility Composable function is provided for applications to detect the ScreenType based on the Android Developers' recommendation documented in Support different screen sizes.

@Composable
fun getScreenType(): ScreenType {
    val screenWidth =
        LocalContext.current.resources.displayMetrics.widthPixels.dp / LocalDensity.current.density
    return when {
        screenWidth < 600.dp -> ScreenType.SMALL
        screenWidth >= 600.dp && screenWidth < 840.dp -> ScreenType.MEDIUM
        screenWidth > 840.dp -> ScreenType.LARGE
        else -> ScreenType.SMALL
    }
}

In order to create the ListCardGrid with custom text color or styles, use the following Composable function:

@Composable
fun ListCardGrid(
    cardList: List<ListCardDataCustomStyleForCollection>,
    screenType: Enum<ScreenType>,
    modifier: Modifier = Modifier
) {
    ...
}

Where the ListCardDataCustomStyleForCollection is comprised of:

data class ListCardDataCustomStyleForCollection(
    val data: ListCardData,
    val textColors: @RawValue ListCardTextColors? = null,
    val textStyles: @RawValue ListCardTextStyles? = null
) : Parcelable

3. How to Customize ListCard Text Color and Style

The default colors and styles of the text in a List Card adhere to the Fiori Horizon design. However, users can also customize them with their own colors and styles. In order to change the text color and text style, use the ListCardDefaults.textColors() and ListCardDefaults.textStyles() Composable functions, respectively, and pass in the required colors and styles for the different texts seen within the List Card.

val customTitleColor = MaterialTheme.fioriHorizonAttributes.SapFioriColorSemanticCritical
val customSubtitleColor = MaterialTheme.fioriHorizonAttributes.SapFioriColorAccent4
val customHeaderColor = MaterialTheme.fioriHorizonAttributes.SapFioriColorSemanticNegative
val customFootnoteColor = MaterialTheme.fioriHorizonAttributes.SapFioriColorAccent12

val customTitleStyle = FioriTextStyleH6
val customSubtitleStyle = FioriTextStyleSubtitle2
val customFootnoteStyle = FioriTextStyleSubtitle3
val customHeaderStyle = FioriTextStyleBody1
val customStatusStyle = FioriTextStyleBody1

FioriHorizonTheme {
    ListCard(
        data = generateListCardExample(),
        textColors = ListCardDefaults.textColors(
            titleColor = customTitleColor,
            subtitleColor = customSubtitleColor,
            headerColor = customHeaderColor,
            footnoteColor = customFootnoteColor
        ),
        textStyles = ListCardDefaults.textStyles(
            titleStyle = customTitleStyle,
            subtitleStyle = customSubtitleStyle,
            footnoteStyle = customFootnoteStyle,
            headerStyle = customHeaderStyle,
            statusStyle = customStatusStyle
        )
    )
}

Last update: February 20, 2023