### Install Catalog App Source: https://github.com/leboncoin/spark-android/blob/main/README.md Build and install the catalog app locally to browse all components. This command uses Gradle to perform the installation. ```bash ./gradlew :catalog:installDebug ``` -------------------------------- ### Full Example: Listing Card with Placeholders Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/placeholder/Placeholder.md This example demonstrates how to use `illustrationPlaceholder` for an image and `textPlaceholder` for text elements within a `ListingCard` composable to show a loading state. ```kotlin @Composable fun ListingCard(listing: Listing?, isLoading: Boolean) { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { AsyncImage( model = listing?.imageUrl, contentDescription = null, modifier = Modifier .fillMaxWidth() .height(200.dp) .illustrationPlaceholder( visible = isLoading, shape = SparkTheme.shapes.medium ) ) Text( text = listing?.title.orEmpty(), modifier = Modifier.textPlaceholder(visible = isLoading) ) Text( text = listing?.price.orEmpty(), modifier = Modifier.textPlaceholder(visible = isLoading) ) } } ``` -------------------------------- ### Define Component Examples in Kotlin Source: https://github.com/leboncoin/spark-android/blob/main/docs/COMPONENT_CREATION_GUIDE.md Use the Example class to define interactive component variants for the catalog. Ensure the source URL points to the correct sample file. ```kotlin // catalog/src/main/kotlin/com/adevinta/spark/catalog/examples/samples/{package}/ private const val ComponentNameExampleDescription = "ComponentName examples" private const val ComponentNameExampleSourceUrl = "$SampleSourceUrl/ComponentNameSamples.kt" public val ComponentNameExamples: ImmutableList = persistentListOf( Example( id = "filled", name = "Filled ComponentName", description = ComponentNameExampleDescription, sourceUrl = ComponentNameExampleSourceUrl, ) { ComponentName.Filled() }, Example( id = "outlined", name = "Outlined ComponentName", description = ComponentNameExampleDescription, sourceUrl = ComponentNameExampleSourceUrl, ) { ComponentName.Outlined() }, ) ``` -------------------------------- ### Install and Initialize Git LFS Source: https://github.com/leboncoin/spark-android/blob/main/docs/CONTRIBUTING.md Install Git Large File Storage (LFS) using Homebrew and initialize it for the repository. Git LFS is required for managing large binary files, such as screenshot test assets. ```bash # Install brew install git-lfs # Check git lfs install ``` -------------------------------- ### Basic Dropdown Usage Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/textfields/Dropdowns.md Demonstrates the minimal setup for a single-selection Dropdown. Requires state variables for options, expanded state, and selected option. ```kotlin private val options = listOf("Option 1", "Option 2", "Option 3", "Option 4", "Option 5") private var expanded by remember { mutableStateOf(false) } private var selectedOptionText by remember { mutableStateOf(options[0]) } Dropdown( value = selectedOptionsText, label = "label", onDismissRequest = { expanded = false }, expanded = expanded, onExpandedChange = { expanded = !expanded }, ) { options.forEach { selectionOption -> DropdownMenuItem( onClick = { selectedOptionText = selectionOption expanded = false }, ) { Text(text = selectionOption) } } } ``` -------------------------------- ### Basic SegmentedGauge Implementations Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/gauge/SegmentedGauge.md Examples showing various configurations for both five-segment and three-segment gauges, including custom colors and sizes. ```kotlin // Five-segment gauge for quality rating SegmentedGauge(type = GaugeTypeFive.VeryHigh) { Text("Very high quality") } // Three-segment gauge for potential indicator SegmentedGaugeShort(type = GaugeTypeThree.Low) { Text("Low potential") } // Custom colored gauge SegmentedGauge( type = GaugeTypeFive.Medium, color = Color.Blue ) { Text("Medium custom color") } // Different sizes SegmentedGauge( size = GaugeSize.Small, type = GaugeTypeFive.High ) { Text("Small gauge") } ``` -------------------------------- ### Basic Single Choice ComboBox Implementation Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/textfields/ComboBoxs.md A simple example showing how to initialize and populate a SingleChoiceComboBox. ```kotlin @Composable fun BasicSingleChoiceComboBox() { val state = rememberTextFieldState() var expanded by remember { mutableStateOf(false) } SingleChoiceComboBox( state = state, expanded = expanded, onExpandedChange = { expanded = it }, onDismissRequest = { expanded = false }, label = "Select an option", placeholder = "Choose an option", ) { for (i in 1..5) { DropdownMenuItem( text = { Text("Option $i") }, selected = false, onClick = { expanded = false } ) } } } ``` -------------------------------- ### Checkbox Group Example Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/toggles/CheckBox.md A conceptual example demonstrating how to manage states for a group of checkboxes, including a parent `TriStateCheckbox` that reflects the state of dependent checkboxes. Requires `remember` and `mutableStateOf` for state management. ```kotlin @Composable fun CheckboxSample() { Column { // define dependent checkboxes states val (state, onStateChange) = remember { mutableStateOf(ToggleableState.On) } val (state2, onStateChange2) = remember { mutableStateOf(ToggleableState.On) } // TriStateCheckbox state reflects state of dependent checkboxes val parentState = remember(state, state2) { if (state && state2) ToggleableState.On else if (!state && !state2) ToggleableState.Off else ToggleableState.Indeterminate } // click on TriStateCheckbox can set state for dependent checkboxes val onParentClick = { val s = parentState != ToggleableState.On onStateChange(s) onStateChange2(s) } Option( state = parentState, onClick = onParentClick, ) Column(modifier = Modifier.selectableGroup()) { Option(state, onStateChange) Option(state2, onStateChange2) } } } @Composable fun Option( option: String, onClick: (() -> Unit) ) { CheckboxLabelled( state = checkedState, onClick = onClick, ) { Text(option) } } ``` -------------------------------- ### Verify Android SDK Path Source: https://github.com/leboncoin/spark-android/blob/main/docs/CONTRIBUTING.md Check if the ANDROID_HOME environment variable is set correctly. This path points to your Android SDK installation. ```bash echo $ANDROID_HOME # Should output something like: /Users/username/Library/Android/sdk ``` -------------------------------- ### Configure Contrast Variant Screenshot Source: https://github.com/leboncoin/spark-android/blob/main/docs/COMPONENT_CREATION_GUIDE.md Example of passing a custom color lambda for variants requiring non-default backgrounds. ```kotlin @Test fun componentNameContrast() = paparazzi.sparkDocSnapshot( color = { SparkTheme.colors.backgroundVariant }, ) { ComponentName.Contrast() } ``` -------------------------------- ### Reference component variant Source: https://github.com/leboncoin/spark-android/blob/main/docs/COMPONENT_CREATION_GUIDE.md Example of referencing a specific component variant in documentation. ```kotlin ComponentName.FirstVariant() ``` -------------------------------- ### Build Spark Android Project Source: https://github.com/leboncoin/spark-android/blob/main/docs/CONTRIBUTING.md Build the entire Spark Android project using the Gradle wrapper script. This command verifies the project setup and compiles all modules. ```bash ./gradlew build ``` -------------------------------- ### Install OpenJDK 17 on macOS Source: https://github.com/leboncoin/spark-android/blob/main/docs/CONTRIBUTING.md Install OpenJDK version 17 using Homebrew on macOS. This version is required by the current Android Gradle Plugin (AGP) used in the project. ```bash brew install openjdk@17 ``` -------------------------------- ### showSnackbar / SnackbarHostState Before Source: https://github.com/leboncoin/spark-android/blob/main/UPGRADING.md Example of using `showSnackbar` with `SnackbarHostState` before the API changes in version 2.0. Note the `style` and `actionOnNewLine` parameters. ```kotlin snackbarHostState.showSnackbar( message = "Saved", style = SnackbarStyle.Filled, actionOnNewLine = false, ) ``` -------------------------------- ### BottomSheet with No Handle Example Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/bottomsheet/BottomSheet.md Demonstrates how to use the BottomSheet component without displaying the drag handle, useful for specific UI designs. ```APIDOC ## BottomSheet with No Handle ### Description This example shows how to configure the `BottomSheet` component to hide the drag handle by setting `showHandle` to `false`. It also illustrates how content can extend behind the handle area when `contentTopPadding` is set to `0.dp`. ### Parameters - **onDismissRequest** (Function) - Required - Callback for dismiss. - **modifier** (Modifier) - Optional - Modifier for the bottom sheet. - **showHandle** (Boolean) - Optional - Set to `false` to hide the handle. Defaults to true. - **contentTopPadding** (Dp) - Required - Set to `0.dp` to allow content to extend behind the handle area. - **sheetState** (SheetState) - Optional - State of the bottom sheet. - **content** (Composable) - Required - The composable content. ### Request Example ```kotlin BottomSheet( onDismissRequest = { /* dismiss action */ }, showHandle = false, contentTopPadding = 0.dp, content = { Box( contentAlignment = Alignment.TopCenter, ) { Image( modifier = Modifier .height(500.dp) .fillMaxWidth(), model = "https://upload.wikimedia.org/wikipedia/commons/f/fd/Pink_flower.jpg", contentScale = ContentScale.Crop, contentDescription = null, ) } } ) ``` ### Response N/A (UI component) ``` -------------------------------- ### CenterAlignedTopAppBar Example Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/appbar/AppBar.md A CenterAlignedTopAppBar with a centered title and a navigation icon. This variant is suitable for screens where a centered title is preferred. ```kotlin CenterAlignedTopAppBar( title = { Text("My Profile") }, navigationIcon = { UpNavigationIcon(onClick = onNavigateUp) }, ) ``` -------------------------------- ### Apply and Check Code Formatting with Spotless Source: https://github.com/leboncoin/spark-android/blob/main/docs/CONTRIBUTING.md Use these Gradle commands to automatically format your code and verify compliance with project style guidelines enforced by Spotless and KtLint. ```bash # Apply formatting automatically ./gradlew spotlessApply ``` ```bash # Verify formatting compliance ./gradlew spotlessCheck ``` -------------------------------- ### Basic File Upload Usage Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/fileupload/FileUpload.md Demonstrates how to implement single and multiple file uploads using `FileUpload.ButtonSingleSelect` and `FileUpload.Button`. Use `maxFiles` to limit the number of selected files. ```kotlin // Single file upload var selectedFile by remember { mutableStateOf(null) } FileUpload.ButtonSingleSelect( onResult = { file -> selectedFile = file }, label = "Select file", ) // Multiple file upload with max limit var selectedFiles by remember { mutableStateOf>(persistentListOf()) } FileUpload.Button( onResult = { files -> selectedFiles = files }, label = "Select files", maxFiles = 5, // Optional: limit to 5 files ) ``` -------------------------------- ### Basic TextField Implementation Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/textfields/TextFields.md Minimal configuration for a TextField including a label, placeholder, and leading icon. ```kotlin TextField( value = "Input", label = { Text("Label") }, placeholder = { Text("Placeholder") }, leadingContent = { Icon( imageVector = Icons.Filled.ArrowDropDown, contentDescription = null ) }, onValueChange = { newValue -> } ) ``` -------------------------------- ### Placeholder with Custom Shape Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/placeholder/Placeholder.md To override the default shape of `Modifier.placeholder`, apply `Modifier.clip` before it. This example clips the placeholder to a circle. ```kotlin Box( modifier = Modifier .size(40.dp) .clip(CircleShape) .placeholder(visible = isLoading) ) ``` -------------------------------- ### Run Automated Code Formatting Source: https://github.com/leboncoin/spark-android/blob/main/docs/COMPONENT_CREATION_GUIDE.md Use Gradle tasks to apply or verify code formatting compliance via Spotless and KtLint. ```bash # Apply formatting automatically ./gradlew spotlessApply # Verify formatting compliance ./gradlew spotlessCheck ``` -------------------------------- ### Generate API Documentation with Dokka Source: https://github.com/leboncoin/spark-android/blob/main/docs/CONTRIBUTING.md Execute this Gradle task to generate multi-module HTML API documentation for the project. The output will be in the build/dokka folder. ```bash ./gradlew dokkaHtmlMultiModule --no-configuration-cache ``` -------------------------------- ### Custom Single File Preview Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/fileupload/FileUpload.md Manage state to create a custom preview for a single selected file. Use `PreviewFile` for a pre-built preview component. ```kotlin var selectedFile by remember { mutableStateOf(null) } FileUpload.ButtonSingleSelect( onResult = { file -> selectedFile = file }, label = "Upload", ) // Custom preview for single file selectedFile?.let { Row { Icon(LeboncoinIcons.Check) Text(it.name) } } // Or use PreviewFile selectedFile?.let { PreviewFile( file = it, onClear = { selectedFile = null } ) } ``` -------------------------------- ### Custom Multiple File Preview Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/fileupload/FileUpload.md Implement custom previews for multiple selected files by iterating through the list. `PreviewFile` can be used for each file in the list. ```kotlin var selectedFiles by remember { mutableStateOf>(persistentListOf()) } FileUpload.Button( onResult = { files -> selectedFiles = files }, label = "Upload multiple", ) // Custom preview for multiple files selectedFiles.forEach { file -> PreviewFile( file = file, onClear = { selectedFiles = selectedFiles.remove(file) } ) } ``` -------------------------------- ### showSnackbar with SnackbarSparkVisuals Source: https://github.com/leboncoin/spark-android/blob/main/UPGRADING.md Shows how to use `showSnackbar` with a `SnackbarSparkVisuals` object for full control over Snackbar appearance and behavior, as recommended after version 2.0. ```kotlin snackbarHostState.showSnackbar( SnackbarSparkVisuals( message = "Saved", intent = SnackbarIntent.Success, title = "Done", withDismissAction = true, ) ) ``` -------------------------------- ### Implement a basic ChipOutlined Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/chips/Chip.md Draws a chip with an optional leading icon and text using the Outlined style. ```kotlin ChipOutlined( text = "default chip", leadingIcon = LeboncoinIcons.CalendarOutline, onClick = {}, ) ``` -------------------------------- ### Multi-Selection Dropdown Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/textfields/Dropdowns.md Example of a multi-selection Dropdown. Manages selected items in a list and updates the displayed value with a suffix for multiple selections. The dropdown can optionally remain expanded after selection. ```kotlin // Your data source from your state model private val DropdownStubData = persistentListOf( "To Kill a Mockingbird", "War and Peace", "The Idiot", "A Picture of Dorian Gray", "1984", "Pride and Prejudice", ) private val multiSelectionFilter by remember { mutableStateOf(DropdownStubData) } private var selectedItems by remember { mutableStateOf(listOf("To Kill a Mockingbird", "War and Peace")) } private val multiSelectedValues by remember(selectedItems.size) { derivedStateOf { val suffix = if (selectedItems.size > 1) ", +${selectedItems.size - 1}" else "" selectedItems.firstOrNull().orEmpty() + suffix } } SelectTextField( value = multiSelectedValues, // ... ) { multiSelectionFilter.forEach { book -> // This should be part of your model otherwise it's a huge work that done on // each items but we're simplifying things since it's an example here. val isSelected = book in selectedItems DropdownMenuItem( text = { Text( text = book, style = if (isSelected) { SparkTheme.typography.body1.highlight } else { SparkTheme.typography.body1 }, ) }, onClick = { selectedItems = if (book in selectedItems) { selectedItems - book } else { selectedItems + book } // Here we want to have the dropdown to stay expanded when we multiSelect but // you could use the line below if in the case you want to have the same behaviour // as the single selection. // expanded = false }, ) } } ``` -------------------------------- ### Implement Documentation Screenshots Source: https://github.com/leboncoin/spark-android/blob/main/docs/CONTRIBUTING.md Use the sparkDocSnapshot extension to generate side-by-side light and dark mode images for component documentation. ```kotlin internal class ChipDocumentationScreenshots { @get:Rule val paparazzi = paparazziRule( renderingMode = SHRINK, deviceConfig = DefaultTestDevices.DocPhone, ) @Test fun chipOutlined() = paparazzi.sparkDocSnapshot { ChipOutlined(text = "Outlined chip", onClick = {}) } } ``` ```kotlin // ButtonContrast needs a darker background to be visible fun buttonContrast() = paparazzi.sparkDocSnapshot(color = { SparkTheme.colors.backgroundVariant }) { ButtonContrast(text = "Contrast button", onClick = {}) } ``` -------------------------------- ### Apply medium shape token to a Box Source: https://github.com/leboncoin/spark-android/blob/main/docs/theming.md This example shows how to apply the `medium` shape token from `SparkTheme.shapes` to clip a `Box` composable. The `medium` token typically corresponds to a 12dp corner radius. ```kotlin Box( modifier = Modifier .clip(SparkTheme.shapes.medium) .background(SparkTheme.colors.surface) ) ``` -------------------------------- ### Get foreground color for a background color Source: https://github.com/leboncoin/spark-android/blob/main/docs/theming.md Use the `contentColorFor` function to retrieve the appropriate foreground color that pairs with a given background color. This ensures text and icons are legible against different backgrounds. ```kotlin val fg = SparkTheme.colors.contentColorFor(SparkTheme.colors.mainContainer) ``` -------------------------------- ### Display a 'New' badge using theme colors and typography Source: https://github.com/leboncoin/spark-android/blob/main/docs/theming.md This composable demonstrates how to use theme colors (`SparkTheme.colors.main`, `SparkTheme.colors.onMain`) and typography (`SparkTheme.typography.caption`) to create a branded badge. Ensure `SparkTheme` is applied in the parent. ```kotlin @Composable fun BrandBadge() { Box( modifier = Modifier .background(SparkTheme.colors.main) .padding(8.dp) ) { Text( text = "New", color = SparkTheme.colors.onMain, style = SparkTheme.typography.caption, ) } } ``` -------------------------------- ### Component Module File Structure Source: https://github.com/leboncoin/spark-android/blob/main/docs/COMPONENT_CREATION_GUIDE.md Standard directory layout for new components, generated via the scaffold script. ```text spark/src/main/kotlin/com/adevinta/spark/components/{package}/ ├── Component.kt # Public API object with variant functions ├── SparkComponent.kt # @InternalSparkApi implementation ├── ComponentDefaults.kt # Default values, constants, helper functions └── Component.md # Component documentation spark-screenshot-testing/src/test/kotlin/com/adevinta/spark/components/{package}/ └── ComponentScreenshot.kt # Paparazzi regression + documentation screenshots catalog/src/main/kotlin/com/adevinta/spark/catalog/ ├── configurator/samples/{package}/ComponentConfigurator.kt └── examples/samples/{package}/ComponentExamples.kt ``` -------------------------------- ### Customizing State Announcement with Suffix (Euros) Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/meter/Meter.md Demonstrates how to use the 'suffix' parameter with a custom 'formatValue' to control the TalkBack announcement for a meter displaying currency. ```kotlin // TalkBack announces: "120 euros" Meter.Circular( value = 120f, range = 0f..200f, content = CircularMeterContent.Value(formatValue = { "${it.toInt()}" }), suffix = " euros", ) ``` -------------------------------- ### Reference documentation screenshot path Source: https://github.com/leboncoin/spark-android/blob/main/docs/COMPONENT_CREATION_GUIDE.md Standard file path pattern for documentation screenshots. ```text ../../images/com.adevinta.spark.components.chips_ChipDocumentationScreenshots_chipOutlined.png ``` -------------------------------- ### Implement Component Configurator Source: https://github.com/leboncoin/spark-android/blob/main/docs/COMPONENT_CREATION_GUIDE.md Create a Configurator to allow real-time parameter adjustment for components. Implement the UI logic within the Composable sample function. ```kotlin // catalog/src/main/kotlin/com/adevinta/spark/catalog/configurator/samples/{package}/ public val ComponentNameConfigurator: ImmutableList = persistentListOf( Configurator( id = "componentname", name = "ComponentName", description = "ComponentName configuration", sourceUrl = "$SampleSourceUrl/ComponentNameSamples.kt", ) { _, _ -> ComponentNameSample() }, ) @Composable private fun ColumnScope.ComponentNameSample() { // TODO: Add configurator implementation } ``` -------------------------------- ### Create Documentation Screenshot Tests Source: https://github.com/leboncoin/spark-android/blob/main/docs/COMPONENT_CREATION_GUIDE.md Test class for generating committed screenshots used in documentation. ```kotlin internal class ComponentNameDocumentationScreenshots { @get:Rule val paparazzi = paparazziRule( renderingMode = SHRINK, deviceConfig = DefaultTestDevices.DocPhone, ) @Test fun componentNameFilled() = paparazzi.sparkDocSnapshot { ComponentName.Filled() } @Test fun componentNameOutlined() = paparazzi.sparkDocSnapshot { ComponentName.Outlined() } } ``` -------------------------------- ### Minimal Popover Usage Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/popover/Popover.md Demonstrates the basic implementation of the Popover component, requiring content and an anchor. The popover is shown when the anchor button is clicked. ```kotlin Popover( popover = { Text( text = "Do you want to have this cookie now?", ) }, isDismissButtonEnabled = true, popoverState = popoverState, ) { ButtonOutlined( text = "Display Popover", onClick = { scope.launch { popoverState.show() } }, ) } ``` -------------------------------- ### Display Compact Rating Summary Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/rating/Rating.md Use RatingSimple for a compact single-star summary. Annotated @ExperimentalSparkApi. ```kotlin RatingSimple( value = 4.5f, commentCount = 12, ) ``` -------------------------------- ### ModalScaffold Implementation Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/dialog/Dialog.md A full-screen flow using ModalScaffold with primary and support buttons. ```kotlin var showModal by remember { mutableStateOf(false) } if (showModal) { ModalScaffold( onClose = { showModal = false }, title = { Text("Edit profile") }, mainButton = { modifier -> ButtonFilled( modifier = modifier, text = "Save", onClick = { viewModel.save(); showModal = false }, ) }, supportButton = { modifier -> ButtonOutlined( modifier = modifier, text = "Discard", onClick = { showModal = false }, ) }, ) { innerPadding -> ProfileForm(modifier = Modifier.padding(innerPadding)) } } ``` -------------------------------- ### AlertDialog Implementation Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/dialog/Dialog.md A standard implementation of AlertDialog using ButtonGhost for action buttons. ```kotlin var showDialog by remember { mutableStateOf(false) } if (showDialog) { AlertDialog( onDismissRequest = { showDialog = false }, title = { Text("Delete listing?") }, text = { Text("This action cannot be undone.") }, confirmButton = { ButtonGhost( text = "Delete", onClick = { viewModel.delete(); showDialog = false }, ) }, dismissButton = { ButtonGhost( text = "Cancel", onClick = { showDialog = false }, ) }, ) } ``` -------------------------------- ### Integrating FileUploadPattern with Custom Components Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/fileupload/FileUpload.md Shows how to use `FileUploadPattern` to add file upload functionality to custom UI elements like Buttons and Surfaces. It also demonstrates direct launcher access for use cases like TextField trailing icons. ```kotlin val pattern = rememberFileUploadPattern( type = FileUploadType.Image(ImageSource.Gallery), mode = FileUploadMode.Single, onFilesSelect = { files -> /* handle files */ } ) // With a Button FileUploadPattern(pattern = pattern) { onClick -> ButtonFilled( onClick = onClick, text = "Upload Image" ) } // With a Surface FileUploadPattern(pattern = pattern) { onClick -> Surface( onClick = onClick, modifier = Modifier.padding(16.dp) ) { Text("Tap to upload") } } // With direct launcher access (e.g., in a TextField trailing icon) TextField( trailingIcon = { IconButton(onClick = { pattern.launchPicker() }) { Icon(LeboncoinIcons.Upload) } } ) ``` -------------------------------- ### Default File Preview Implementation Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/fileupload/FileUpload.md Utilizes `FileUploadList` to display selected files with progress bars, error states, and clear buttons. This component works in conjunction with `FileUpload.Button` to manage and visualize uploaded files. ```kotlin FileUpload.Button( onResult = { files -> selectedFiles = files }, label = "Select files", ) FileUploadList( files = selectedFiles, onClearFile = { file -> selectedFiles = selectedFiles.remove(file) }, onClick = { file -> /* handle file click */ } ) ``` -------------------------------- ### Implement a Contrast Button Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/buttons/Buttons.md Use for high to mid-priority actions when placed on dark backgrounds, such as images or videos. ```kotlin ButtonContrast( text = "Main", onClick = { /*Click event*/ }, ) ``` -------------------------------- ### Customizing State Announcement with Suffix (Stars) Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/meter/Meter.md Demonstrates how to use the 'suffix' parameter with a custom 'formatValue' to control the TalkBack announcement for a meter displaying a rating. ```kotlin // TalkBack announces: "4.5 stars" Meter.Circular( value = 4.5f, range = 0f..5f, content = CircularMeterContent.Value(formatValue = { it.toString() }), suffix = " stars", ) ``` -------------------------------- ### Basic Multi Choice ComboBox in Kotlin Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/textfields/ComboBoxs.md Demonstrates how to implement a basic multi-choice ComboBox using Jetpack Compose. Ensure you have the necessary state management and Composable functions imported. ```kotlin import androidx.compose.runtime.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.Check import androidx.compose.ui.Alignment import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.sp import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.add import kotlinx.collections.immutable.filter import kotlinx.collections.immutable.toPersistentList // Assuming SelectedChoice and MultiChoiceComboBox are defined elsewhere // For demonstration purposes, let's define them minimally here: data class SelectedChoice(val id: String, val label: String) @Composable fun MultiChoiceComboBox( state: TextFieldState, expanded: Boolean, onExpandedChange: (Boolean) -> Unit, onDismissRequest: () -> Unit, selectedChoices: PersistentList, onSelectedClick: (String) -> Unit, label: String, placeholder: String, content: @Composable () -> Unit ) { // This is a placeholder implementation for MultiChoiceComboBox // In a real scenario, this would be a more complex Composable OutlinedTextField( value = state.text, onValueChange = { state.text = it }, label = { Text(label) }, placeholder = { Text(placeholder) }, trailingIcon = { IconButton(onClick = { onExpandedChange(!expanded) }) { Icon(Icons.Default.ArrowDropDown, contentDescription = "Dropdown") } }, readOnly = true ) if (expanded) { DropdownMenu( expanded = expanded, onDismissRequest = onDismissRequest ) { content() } } } interface TextFieldState { var text: String } @Composable fun rememberTextFieldState(): TextFieldState { return remember { object : TextFieldState { override var text by mutableStateOf("") } } } @Composable fun BasicMultiChoiceComboBox() { val state = rememberTextFieldState() var expanded by remember { mutableStateOf(false) } var selectedChoices by remember { mutableStateOf(persistentListOf()) } MultiChoiceComboBox( state = state, expanded = expanded, onExpandedChange = { expanded = it }, onDismissRequest = { expanded = false }, selectedChoices = selectedChoices, onSelectedClick = { id -> selectedChoices = selectedChoices.filter { it.id != id }.toPersistentList() }, label = "Select options", placeholder = "Choose options", ) { for (i in 1..5) { DropdownMenuItem( text = { Text("Option $i") }, selected = selectedChoices.any { it.id == "option$i" }, onClick = { val choice = SelectedChoice("option$i", "Option $i") selectedChoices = if (selectedChoices.any { it.id == choice.id }) { selectedChoices.filter { it.id != choice.id }.toPersistentList() } else { selectedChoices.add(choice) } } ) } } } ``` -------------------------------- ### Implement an Outlined Button Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/buttons/Buttons.md Use for secondary, support actions. Avoid placing on top of images due to visibility constraints. ```kotlin ButtonOutlined( text = "Main", onClick = { /*Click event*/ }, ) ``` -------------------------------- ### Basic Slider Implementation Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/slider/Sliders.md Use this snippet to implement a basic slider with a minimum and maximum value. Requires `SliderIntent` for styling. ```kotlin Slider( value = 0.5f, intent = SliderIntent.Success, onValueChange = {}, ) ``` -------------------------------- ### Basic Checkbox Usage Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/toggles/CheckBox.md Demonstrates the minimal usage of the Checkbox component. Ensure `remember` and `mutableStateOf` are imported for state management. ```kotlin var checkedState by remember { mutableStateOf(ToggleableState.On) } Checkbox( state = checkedState, onClick = { isChecked = !isChecked } ) ``` -------------------------------- ### Clone Spark Android Repository Source: https://github.com/leboncoin/spark-android/blob/main/docs/CONTRIBUTING.md Clone the Spark Android project from GitHub and navigate into the project directory. This is the first step to setting up the development environment. ```bash git clone https://github.com/leboncoin/spark-android.git cd spark-android ``` -------------------------------- ### Validate Binary Compatibility with Gradle Source: https://github.com/leboncoin/spark-android/blob/main/docs/CONTRIBUTING.md Use these Gradle commands to check for binary compatibility issues or to update reference ABI files, ensuring backward compatibility of the library. ```bash # Check for binary compatibility issues ./gradlew checkLegacyAbi ``` ```bash # Update reference ABI files ./gradlew updateLegacyAbi ``` -------------------------------- ### Execute Screenshot Tests Source: https://github.com/leboncoin/spark-android/blob/main/docs/CONTRIBUTING.md Run screenshot verification or record new golden images using Gradle tasks. ```bash # Run all screenshot tests ./gradlew spark-screenshot-testing:verifyPaparazziRelease ``` ```bash # Run all screenshot tests ./gradlew spark-screenshot-testing:cleanRecordPaparazziRelease ``` -------------------------------- ### Implement Highlight Flat Card Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/card/Card.md Creates a flat card featuring a colored banner at the top. ```kotlin Card.HighlightFlat( heading = { } ) { Text("Card with highlight banner") } ``` -------------------------------- ### Implement a Stepper with custom range and status Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/stepper/Stepper.md Demonstrates using a custom step size, suffix, and conditional error status. ```kotlin Stepper( value = weight, onValueChange = { weight = it }, range = 0..500, step = 5, suffix = " kg", status = if (weight == 0) FormFieldStatus.Error else null, ) ``` -------------------------------- ### Basic Horizontal Segmented Control Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/segmentedcontrol/SegmentedControl.md Demonstrates the basic usage of a horizontal segmented control with single-line text segments. Use this for simple selection tasks. ```kotlin var selected by remember { mutableIntStateOf(0) } SegmentedControl.Horizontal(selectedIndex = selected) { singleLine("All", selected = selected == 0, onClick = { selected = 0 }) singleLine("Active", selected = selected == 1, onClick = { selected = 1 }) singleLine("Completed", selected = selected == 2, onClick = { selected = 2 }) } ``` -------------------------------- ### Default Suffix Behavior (Percentage) Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/meter/Meter.md Shows the default behavior when no suffix is provided; TalkBack announces the percentage value derived from the meter's value and range. ```kotlin // suffix = null → TalkBack announces "75%" (from formatValue default) Meter.Circular( value = 75f, content = CircularMeterContent.Value(), ) ``` -------------------------------- ### Implement Flat Card Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/card/Card.md Creates a flat card with subtle background separation. ```kotlin Card.Flat { Text("This is a flat card with default styling.") } ``` -------------------------------- ### Component Generator Script Invocation Source: https://github.com/leboncoin/spark-android/blob/main/docs/CONTRIBUTING.md Commands to run the scaffolding script for new components. ```bash ./scripts/generate-component.main.kts ``` ```bash ./scripts/generate-component.main.kts \ --component-name Card \ --package-name card \ -v Elevated -v Outlined ``` -------------------------------- ### Apply typography styles Source: https://github.com/leboncoin/spark-android/blob/main/docs/theming.md Use predefined typography tokens from the SparkTheme to style text components. ```kotlin Text( text = "Price", style = SparkTheme.typography.headline2, ) ``` ```kotlin Text( text = "Important", style = SparkTheme.typography.body2.highlight, ) ``` -------------------------------- ### Standard Component File Structure Source: https://github.com/leboncoin/spark-android/blob/main/docs/CONTRIBUTING.md The required directory layout for every component in the Spark library. ```text spark/src/main/kotlin/com/adevinta/spark/components/{component-name}/ ├── Component.kt # Public API surface ├── ComponentDefaults.kt # Design token mappings and default values ├── ComponentIntent.kt # Semantic color variants ├── ComponentSize.kt # Size specifications ├── ComponentState.kt # Behavioral state management (if needed) └── Component.md # API documentation and usage guidelines ``` -------------------------------- ### Reference Documentation Images in Markdown Source: https://github.com/leboncoin/spark-android/blob/main/docs/CONTRIBUTING.md Embed generated screenshots into component documentation files using relative paths following the Paparazzi naming convention. ```markdown ![](../../images/com.adevinta.spark.components.chips_ChipDocumentationScreenshots_chipOutlined.png) ``` -------------------------------- ### Tag Layout with FlowRow Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/tags/Tag.md Recommended layout approach using FlowRow to manage multiple tags with spacing and row constraints. ```kotlin FlowRow( horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), maxItemsInEachRow = 4, ) { TagFilled(text = "Tag 1", intent = TagIntent.Main) TagFilled(text = "Tag longer 2", intent = TagIntent.Accent) TagFilled(text = "Tag a bit longer 3", intent = TagIntent.Info) TagTinted(text = "Tag way more longer 4", intent = TagIntent.Main) TagTinted(text = "Tag small 5", intent = TagIntent.Main) TagOutlined(text = "Tag 6", intent = TagIntent.Main) } ``` -------------------------------- ### Implement BottomSheetScaffold Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/bottomsheet/BottomSheet.md Scaffold component that integrates a bottom sheet with screen content, supporting custom padding and swipe behavior. ```kotlin fun BottomSheetScaffold( sheetContent = { Text( text = "Sheet Content", modifier = Modifier.fillMaxWidth().padding(16.dp), ) }, content = { Text( text = "Screen Content", modifier = Modifier.fillMaxWidth().padding(16.dp), ) }, modifier: Modifier = Modifier, scaffoldState: BottomSheetScaffoldState = rememberBottomSheetScaffoldState(), showHandle: Boolean = true, sheetContentTopPadding: Dp = if (showHandle) ContentTopPadding else ContentTopPaddingNoHandle, screenContentPadding: Dp = ContentTopPadding, sheetSwipeEnabled: Boolean = true, topBar: @Composable (() -> Unit)? = null, snackbarHost: @Composable (androidx.compose.material3.SnackbarHostState) -> Unit, ) ``` -------------------------------- ### Meter Circular Basic Usage Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/meter/Meter.md Demonstrates the basic usage of the Meter.Circular component with a value and default content. ```kotlin Meter.Circular( value = 70f, content = CircularMeterContent.Value(), ) ``` -------------------------------- ### Opening Files with System Default Application Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/fileupload/FileUpload.md Use `FileUploadDefaults.openFile()` to open a selected file with the system's default application. Ensure FileProvider is configured for custom file locations on Android. ```kotlin PreviewFile( file = uploadedFile, onClear = { /* remove file */ }, onClick = { // Open file with default application FileUploadDefaults.openFile(uploadedFile) } ) ``` -------------------------------- ### Snackbar Integration with SnackbarHost Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/snackbars/Snackbar.md To display snackbars, integrate them with a `SnackbarHost` within a `Scaffold`. Use `LaunchedEffect` to trigger snackbar visibility based on state changes. ```kotlin private val snackbarHostState = remember { SnackbarHostState() } LaunchedEffect(conversationsState) { if (shouldShowSnackbar) { snackbarHostState.showSnackbar( message = "Message", duration = SnackbarDuration.Short) } } Scaffold( snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, ) { innerPadding -> // Content } ``` -------------------------------- ### Publish to Local Maven Repository Source: https://github.com/leboncoin/spark-android/blob/main/docs/CONTRIBUTING.md Use this Gradle command to publish the Spark Android library to your local Maven repository for testing. ```bash # Publish to local Maven repository ./gradlew publishToMavenLocal -Pversion=x.x.x-SNAPSHOT ``` -------------------------------- ### Create NavigationBar with Items Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/appbar/AppBar.md A persistent bottom destination switcher. Use it with three to five NavigationBarItems. The label is always shown for the selected item. ```kotlin var selected by remember { mutableIntStateOf(0) } NavigationBar { NavigationBarItem( icon = SparkIcons.HomeFill, label = { Text("Home") }, selected = selected == 0, onClick = { selected = 0 }, ) NavigationBarItem( icon = SparkIcons.SearchFill, label = { Text("Search") }, selected = selected == 1, onClick = { selected = 1 }, ) NavigationBarItem( icon = SparkIcons.AccountFill, label = { Text("Profile") }, selected = selected == 2, onClick = { selected = 2 }, ) } ``` -------------------------------- ### Define component documentation structure Source: https://github.com/leboncoin/spark-android/blob/main/docs/COMPONENT_CREATION_GUIDE.md Standard markdown structure for component documentation files. ```markdown # Package com.adevinta.spark.components.{package} [ComponentName](https://spark.adevinta.com/...) TODO: Add component description. ![](../../images/com.adevinta.spark.components.{package}_{ComponentName}DocumentationScreenshots_{variantMethodName}.png) The minimal usage of the component is: ```kotlin ComponentName.FirstVariant() ``` ``` -------------------------------- ### Add Buttons to Card Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/card/Card.md Demonstrates including a ButtonFilled within a card layout. ```kotlin Card.Elevated { Column { Text("Card Title") Text("Card description") ButtonFilled( text = "Action", onClick = { } ) } } ``` -------------------------------- ### PreviewFile Component Customization Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/fileupload/FileUpload.md Customize the `PreviewFile` component using various parameters like `modifier`, `progress`, `isLoading`, `errorMessage`, `enabled`, `clearContentDescription`, `onClick`, and `clearIcon`. ```kotlin PreviewFile( file = uploadedFile, onClear = { /* remove file */ }, modifier = Modifier, // modifier for the component progress = { 0.5f }, // determinate progress (0.0 to 1.0) isLoading = false, // indeterminate loading state errorMessage = null, // error message to display enabled = true, // whether the component is enabled clearContentDescription = "Remove ${uploadedFile.name}", // accessibility label for the clear/remove file button onClick = { /* handle file click */ }, // makes the preview clickable, use null to make the preview read only clearIcon = LeboncoinIcons.Close, // customize the clear/remove button icon ) ``` -------------------------------- ### RadioGroup Sample Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/toggles/RadioButton.md Provides a sample implementation of a RadioGroup using `Column` and `Modifier.selectableGroup()`. This ensures correct accessibility behavior for a group of radio buttons where only one option can be selected. ```kotlin @Composable fun RadioGroupSample() { val radioOptions = listOf("Calls", "Missed", "Friends") val (selectedOption, onOptionSelected) = remember { mutableStateOf(radioOptions[0]) } // Note that Modifier.selectableGroup() is essential to ensure correct accessibility behavior Column(Modifier.selectableGroup()) { radioOptions.forEach { text -> RadioButtonLabelled( selected = text == selectedOption, onClick = { selectedOption(text) }, ) { Text(text) } } } } ``` -------------------------------- ### Configure BottomSheet with image content Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/bottomsheet/BottomSheet.md Implementation of a BottomSheet where content extends behind the handle by setting contentTopPadding to 0.dp. ```kotlin fun BottomSheet( onDismissRequest: () -> Unit, modifier: Modifier = Modifier, showHandle: Boolean = true, contentTopPadding = 0.dp, sheetState: SheetState = rememberModalBottomSheetState(), content = { Box( contentAlignment = Alignment.TopCenter, ) { Image( modifier = Modifier .height(500.dp) .fillMaxWidth(), model = "https://upload.wikimedia.org/wikipedia/commons/f/fd/Pink_flower.jpg", contentScale = ContentScale.Crop, contentDescription = null, ) } } ) ``` -------------------------------- ### Configure Image Content Scale and Alignment Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/image/Image.md Control how the image content is scaled and aligned within its bounds using `contentScale` and `alignment` parameters, similar to standard Compose Image. ```kotlin Image( model = url, contentDescription = "Banner", modifier = Modifier .fillMaxWidth() .height(200.dp), contentScale = ContentScale.Crop, alignment = Alignment.TopCenter, ) ``` -------------------------------- ### Define KDoc API documentation Source: https://github.com/leboncoin/spark-android/blob/main/docs/COMPONENT_CREATION_GUIDE.md Standard KDoc template for public component APIs. ```kotlin /** * [Component] provides [functional description] following [design principle]. * * This component implements [Material Design pattern] adapted for Spark's * visual language and supports [key capabilities]. * * @param required describing the primary data/behavior requirement * @param modifier [Modifier] to be applied to the component layout * @param intent [ComponentIntent] defining the semantic color treatment * @param enabled Controls interactive state and visual presentation * @param interactionSource [MutableInteractionSource] for observing user interactions * * @sample ComponentBasicSample * @sample ComponentAdvancedSample */ @Composable public fun ComponentName(/* parameters */) { /* implementation */ } ``` -------------------------------- ### Register Component in Catalog Source: https://github.com/leboncoin/spark-android/blob/main/docs/COMPONENT_CREATION_GUIDE.md Add the component to the central registry by defining a Component object and including it in the main Components list. ```kotlin // catalog/src/main/kotlin/com/adevinta/spark/catalog/model/Components.kt private val ComponentName = Component( id = "component-name", name = "Component Display Name", description = R.string.component_description, illustration = R.drawable.component_illustration, // Optional, it'll use the spark logo by default guidelinesUrl = "$ComponentGuidelinesUrl/component-path", docsUrl = "$PackageSummaryUrl/com.adevinta.spark.components.component/index.html", sourceUrl = "$SparkSourceUrl/kotlin/com/adevinta/spark/components/component/Component.kt", examples = ComponentExamples, configurators = listOf(ComponentConfigurator), ) // Add to the main components list at the bottom of the file public val Components: List = listOf( // ... existing components ComponentName, ).sortedBy { it.name } ``` -------------------------------- ### Basic Snackbar Usage Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/snackbars/Snackbar.md The minimal implementation of a Snackbar requires only a message content. This is suitable for simple notifications. ```kotlin Snackbar { Text("Your changes have been saved") } ``` -------------------------------- ### Implement a Tinted Button Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/buttons/Buttons.md Use for medium-emphasis actions as a middle ground between filled and outlined styles. ```kotlin ButtonTinted( text = "Main", onClick = { /*Click event*/ }, ) ``` -------------------------------- ### Implement a minimal Stepper Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/stepper/Stepper.md Use for inline numeric selection where labels are not required. ```kotlin var quantity by remember { mutableStateOf(1) } Stepper( value = quantity, onValueChange = { quantity = it }, range = 1..99, step = 1, ) ``` -------------------------------- ### Implement a basic SwitchLabelled Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/toggles/Switch.md Use SwitchLabelled for a toggle with an associated text label. Requires a state variable to track the checked status. ```kotlin var checked by remember { mutableStateOf(false) } SwitchLabelled( checked = checked, onCheckedChange = { checked = !checked } ) { Text(text = "Switch On") } ``` -------------------------------- ### BottomAppBar with Actions and FAB Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/appbar/AppBar.md A BottomAppBar configured with navigation actions and a FloatingActionButton. The FAB uses `BottomAppBarDefaults.bottomAppBarFabColor` for its color and `FloatingActionButtonDefaults.bottomAppBarFabElevation` for elevation. ```kotlin BottomAppBar( actions = { IconButton(onClick = onEdit) { Icon(SparkIcons.PenOutline, contentDescription = "Edit") } IconButton(onClick = onShare) { Icon(SparkIcons.Share, contentDescription = "Share") } }, floatingActionButton = { FloatingActionButton( onClick = onAdd, containerColor = BottomAppBarDefaults.bottomAppBarFabColor, elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation(), icon = SparkIcons.Plus, contentDescription = "Add", ) }, ) ``` -------------------------------- ### File Type Filtering for Single File Upload Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/fileupload/FileUpload.md Demonstrates how to restrict file selection to specific types using the `type` parameter in `FileUpload.ButtonSingleSelect`. This includes options for images (camera/gallery), videos, combined media, and generic files with extension filtering. ```kotlin // Images only (camera only) FileUpload.ButtonSingleSelect( onResult = { /* handle */ }, label = "Take photo", type = FileUploadType.Image(source = ImageSource.Camera) ) // Images only (gallery only) FileUpload.ButtonSingleSelect( onResult = { /* handle */ }, label = "Choose from gallery", type = FileUploadType.Image(source = ImageSource.Gallery) ) // Videos only FileUpload.ButtonSingleSelect( onResult = { /* handle */ }, label = "Select video", type = FileUploadType.Video ) // Images and videos FileUpload.ButtonSingleSelect( onResult = { /* handle */ }, label = "Select media", type = FileUploadType.ImageAndVideo ) // Generic files with extension filter FileUpload.ButtonSingleSelect( onResult = { /* handle */ }, label = "Select PDF", type = FileUploadType.File(extensions = setOf("pdf")) ) ``` -------------------------------- ### Implement Highlight Elevated Card Source: https://github.com/leboncoin/spark-android/blob/main/spark/src/main/kotlin/com/adevinta/spark/components/card/Card.md Creates an elevated card with a colored top banner for maximum emphasis. ```kotlin Card.HighlightElevated( heading = { } ) { Text("Card with highlight banner and elevation") } ```