feat(search): collapsible section headers + sorting + search improvements
- Collapsible sticky section headers with animated chevron - Full header row clickable to toggle collapse - SearchResultSorter, SearchSortOrder, pseudo-kind filtering - TextFieldValue cursor preservation, relay timeout improvements Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+28
-7
@@ -64,8 +64,10 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -131,6 +133,14 @@ fun SearchScreen(
|
||||
val relayStatuses by relayManager.relayStatuses.collectAsState()
|
||||
val allRelayUrls = remember(relayStatuses) { relayStatuses.keys }
|
||||
val displayText by state.displayText.collectAsState()
|
||||
// Track TextFieldValue locally to preserve cursor position
|
||||
var textFieldValue by remember { mutableStateOf(TextFieldValue(displayText)) }
|
||||
// Sync from flow only when text changes externally (form-driven updates)
|
||||
LaunchedEffect(displayText) {
|
||||
if (textFieldValue.text != displayText) {
|
||||
textFieldValue = TextFieldValue(text = displayText, selection = TextRange(displayText.length))
|
||||
}
|
||||
}
|
||||
val query by state.query.collectAsState()
|
||||
val debouncedQuery by state.debouncedQuery.collectAsState()
|
||||
val panelExpanded by state.panelExpanded.collectAsState()
|
||||
@@ -142,12 +152,19 @@ fun SearchScreen(
|
||||
// Bech32 parsing (immediate, no debounce)
|
||||
val bech32Results = remember(displayText) { parseSearchInput(displayText) }
|
||||
|
||||
// Skip people search when query specifies kinds that don't include profile (kind 0)
|
||||
val shouldSearchPeople =
|
||||
(debouncedQuery.kinds.isEmpty() && debouncedQuery.pseudoKinds.isEmpty()) ||
|
||||
debouncedQuery.kinds.contains(MetadataEvent.KIND)
|
||||
|
||||
// Clear results and start loading when query changes
|
||||
LaunchedEffect(debouncedQuery) {
|
||||
if (!debouncedQuery.isEmpty && bech32Results.isEmpty()) {
|
||||
state.clearResults()
|
||||
state.initRelayStates(allRelayUrls)
|
||||
state.startSearching("people-search")
|
||||
if (shouldSearchPeople) {
|
||||
state.startSearching("people-search")
|
||||
}
|
||||
state.startSearching("adv-search")
|
||||
// Timeout relays that silently ignore NIP-50 (e.g. strfry)
|
||||
kotlinx.coroutines.delay(10_000L)
|
||||
@@ -161,6 +178,10 @@ fun SearchScreen(
|
||||
return@rememberSubscription null
|
||||
}
|
||||
if (bech32Results.isNotEmpty()) return@rememberSubscription null
|
||||
if (!shouldSearchPeople) {
|
||||
state.stopSearching("people-search")
|
||||
return@rememberSubscription null
|
||||
}
|
||||
|
||||
createSearchPeopleSubscription(
|
||||
relays = allRelayUrls,
|
||||
@@ -334,12 +355,11 @@ fun SearchScreen(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value =
|
||||
TextFieldValue(
|
||||
text = displayText,
|
||||
selection = TextRange(displayText.length),
|
||||
),
|
||||
onValueChange = { state.updateFromText(it.text) },
|
||||
value = textFieldValue,
|
||||
onValueChange = {
|
||||
textFieldValue = it
|
||||
state.updateFromText(it.text)
|
||||
},
|
||||
modifier = Modifier.weight(1f).focusRequester(focusRequester),
|
||||
placeholder = { Text("Search notes, people, tags... or use operators") },
|
||||
leadingIcon = {
|
||||
@@ -386,6 +406,7 @@ fun SearchScreen(
|
||||
AdvancedSearchPanel(
|
||||
query = query,
|
||||
onKindsChanged = { state.updateKinds(it) },
|
||||
onPseudoKindsChanged = { state.updatePseudoKinds(it) },
|
||||
onAuthorAdded = { state.addAuthor(it) },
|
||||
onAuthorRemoved = { state.removeAuthor(it) },
|
||||
onDateRangeChanged = { since, until -> state.updateDateRange(since, until) },
|
||||
|
||||
+50
-28
@@ -56,6 +56,7 @@ import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.compose.ui.input.key.onKeyEvent
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.search.ContentPreset
|
||||
import com.vitorpamplona.amethyst.commons.search.DateUtils
|
||||
import com.vitorpamplona.amethyst.commons.search.KindRegistry
|
||||
import com.vitorpamplona.amethyst.commons.search.QueryParser
|
||||
@@ -66,6 +67,7 @@ import com.vitorpamplona.amethyst.commons.search.SearchQuery
|
||||
fun AdvancedSearchPanel(
|
||||
query: SearchQuery,
|
||||
onKindsChanged: (List<Int>) -> Unit,
|
||||
onPseudoKindsChanged: (List<String>) -> Unit,
|
||||
onAuthorAdded: (String) -> Unit,
|
||||
onAuthorRemoved: (String) -> Unit,
|
||||
onDateRangeChanged: (Long?, Long?) -> Unit,
|
||||
@@ -99,10 +101,10 @@ fun AdvancedSearchPanel(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
KindRegistry.presets.forEach { (name, kinds) ->
|
||||
KindRegistry.presets.forEach { (name, preset) ->
|
||||
FilterChip(
|
||||
selected = query.kinds.toList().containsAll(kinds),
|
||||
onClick = { onKindsChanged(toggleKinds(query.kinds.toList(), kinds)) },
|
||||
selected = preset.isSelected(query.kinds.toList(), query.pseudoKinds.toList()),
|
||||
onClick = { togglePreset(preset, query, onKindsChanged, onPseudoKindsChanged) },
|
||||
label = { Text(name) },
|
||||
)
|
||||
}
|
||||
@@ -161,15 +163,29 @@ fun AdvancedSearchPanel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleKinds(
|
||||
current: List<Int>,
|
||||
toggle: List<Int>,
|
||||
): List<Int> =
|
||||
if (current.containsAll(toggle)) {
|
||||
current - toggle.toSet()
|
||||
private fun togglePreset(
|
||||
preset: ContentPreset,
|
||||
query: SearchQuery,
|
||||
onKindsChanged: (List<Int>) -> Unit,
|
||||
onPseudoKindsChanged: (List<String>) -> Unit,
|
||||
) {
|
||||
val pseudo = preset.pseudoKind
|
||||
if (pseudo != null) {
|
||||
val current = query.pseudoKinds.toList()
|
||||
if (pseudo in current) {
|
||||
onPseudoKindsChanged(current - pseudo)
|
||||
} else {
|
||||
onPseudoKindsChanged(current + pseudo)
|
||||
}
|
||||
} else {
|
||||
(current + toggle).distinct()
|
||||
val current = query.kinds.toList()
|
||||
if (current.containsAll(preset.kinds)) {
|
||||
onKindsChanged(current - preset.kinds.toSet())
|
||||
} else {
|
||||
onKindsChanged((current + preset.kinds).distinct())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
@@ -242,19 +258,21 @@ private fun DateRangeFields(
|
||||
until: Long?,
|
||||
onChanged: (Long?, Long?) -> Unit,
|
||||
) {
|
||||
var sinceText by remember(since) {
|
||||
mutableStateOf(
|
||||
since?.let {
|
||||
DateUtils.timestampToDate(it)
|
||||
} ?: "",
|
||||
)
|
||||
// Local text is source of truth while typing.
|
||||
// Only propagate valid timestamps (or null when cleared).
|
||||
// Only sync from external when the timestamp changes to something we didn't produce.
|
||||
var sinceText by remember { mutableStateOf(since?.let { DateUtils.timestampToDate(it) } ?: "") }
|
||||
var lastSince by remember { mutableStateOf(since) }
|
||||
if (since != lastSince) {
|
||||
sinceText = since?.let { DateUtils.timestampToDate(it) } ?: ""
|
||||
lastSince = since
|
||||
}
|
||||
var untilText by remember(until) {
|
||||
mutableStateOf(
|
||||
until?.let {
|
||||
DateUtils.timestampToDate(it)
|
||||
} ?: "",
|
||||
)
|
||||
|
||||
var untilText by remember { mutableStateOf(until?.let { DateUtils.timestampToDate(it) } ?: "") }
|
||||
var lastUntil by remember { mutableStateOf(until) }
|
||||
if (until != lastUntil) {
|
||||
untilText = until?.let { DateUtils.timestampToDate(it) } ?: ""
|
||||
lastUntil = until
|
||||
}
|
||||
|
||||
Row(
|
||||
@@ -271,9 +289,11 @@ private fun DateRangeFields(
|
||||
value = sinceText,
|
||||
onValueChange = {
|
||||
sinceText = it
|
||||
val ts =
|
||||
QueryParser.parseDateToTimestamp(it)
|
||||
onChanged(ts, until)
|
||||
val ts = QueryParser.parseDateToTimestamp(it)
|
||||
if (ts != null || it.isBlank()) {
|
||||
lastSince = ts
|
||||
onChanged(ts, until)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = { Text("YYYY-MM-DD") },
|
||||
@@ -290,9 +310,11 @@ private fun DateRangeFields(
|
||||
value = untilText,
|
||||
onValueChange = {
|
||||
untilText = it
|
||||
val ts =
|
||||
QueryParser.parseDateToTimestamp(it)
|
||||
onChanged(since, ts)
|
||||
val ts = QueryParser.parseDateToTimestamp(it)
|
||||
if (ts != null || it.isBlank()) {
|
||||
lastUntil = ts
|
||||
onChanged(since, ts)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = { Text("YYYY-MM-DD") },
|
||||
|
||||
+141
-46
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.desktop.ui.search
|
||||
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -41,6 +42,8 @@ import androidx.compose.material.icons.filled.Forum
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.FilterChipDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -50,17 +53,20 @@ import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.search.AdvancedSearchBarState
|
||||
import com.vitorpamplona.amethyst.commons.search.KindRegistry
|
||||
import com.vitorpamplona.amethyst.commons.search.SearchSortOrder
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.UserSearchCard
|
||||
import com.vitorpamplona.amethyst.commons.util.toTimeAgo
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
@@ -74,8 +80,10 @@ fun SearchResultsList(
|
||||
modifier: Modifier = Modifier,
|
||||
listState: LazyListState = rememberLazyListState(),
|
||||
) {
|
||||
val people by state.peopleResults.collectAsState()
|
||||
val notes by state.noteResults.collectAsState()
|
||||
val people by state.sortedPeopleResults.collectAsState()
|
||||
val notes by state.sortedNoteResults.collectAsState()
|
||||
val eventSortOrder by state.eventSortOrder.collectAsState()
|
||||
val peopleSortOrder by state.peopleSortOrder.collectAsState()
|
||||
|
||||
val hasResults = people.isNotEmpty() || notes.isNotEmpty()
|
||||
|
||||
@@ -86,6 +94,9 @@ fun SearchResultsList(
|
||||
val articles = notes.filter { it.kind == LongTextNoteEvent.KIND }
|
||||
val otherNotes = notes.filter { it.kind != 1 && it.kind != LongTextNoteEvent.KIND }
|
||||
|
||||
// Per-section collapsed state (absent = expanded)
|
||||
val collapsedSections = remember { mutableStateMapOf<String, Boolean>() }
|
||||
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
@@ -93,25 +104,37 @@ fun SearchResultsList(
|
||||
) {
|
||||
// People section
|
||||
if (people.isNotEmpty()) {
|
||||
val collapsed = collapsedSections["people"] == true
|
||||
stickyHeader(key = "header-people") {
|
||||
SectionHeader("People", people.size, Icons.Default.Person)
|
||||
}
|
||||
val displayPeople = people.take(5)
|
||||
items(displayPeople, key = { "person-${it.pubkeyHex}" }) { user ->
|
||||
UserSearchCard(
|
||||
user = user,
|
||||
onClick = { onNavigateToProfile(user.pubkeyHex) },
|
||||
SortableHeader(
|
||||
title = "People",
|
||||
count = people.size,
|
||||
icon = Icons.Default.Person,
|
||||
options = SearchSortOrder.PEOPLE_OPTIONS,
|
||||
selected = peopleSortOrder,
|
||||
onSelect = { state.updatePeopleSortOrder(it) },
|
||||
collapsed = collapsed,
|
||||
onToggleCollapse = { collapsedSections["people"] = !collapsed },
|
||||
)
|
||||
}
|
||||
if (people.size > 5) {
|
||||
item(key = "people-expand") {
|
||||
ExpandableSection(
|
||||
remaining = people.drop(5),
|
||||
) { user ->
|
||||
UserSearchCard(
|
||||
user = user,
|
||||
onClick = { onNavigateToProfile(user.pubkeyHex) },
|
||||
)
|
||||
if (!collapsed) {
|
||||
val displayPeople = people.take(5)
|
||||
items(displayPeople, key = { "person-${it.pubkeyHex}" }) { user ->
|
||||
UserSearchCard(
|
||||
user = user,
|
||||
onClick = { onNavigateToProfile(user.pubkeyHex) },
|
||||
)
|
||||
}
|
||||
if (people.size > 5) {
|
||||
item(key = "people-expand") {
|
||||
ExpandableSection(
|
||||
remaining = people.drop(5),
|
||||
) { user ->
|
||||
UserSearchCard(
|
||||
user = user,
|
||||
onClick = { onNavigateToProfile(user.pubkeyHex) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,19 +145,31 @@ fun SearchResultsList(
|
||||
if (people.isNotEmpty()) {
|
||||
item(key = "divider-notes") { HorizontalDivider(Modifier.padding(vertical = 4.dp)) }
|
||||
}
|
||||
val collapsed = collapsedSections["notes"] == true
|
||||
stickyHeader(key = "header-notes") {
|
||||
SectionHeader("Notes", textNotes.size, Icons.Default.Description)
|
||||
SortableHeader(
|
||||
title = "Notes",
|
||||
count = textNotes.size,
|
||||
icon = Icons.Default.Description,
|
||||
options = SearchSortOrder.EVENT_OPTIONS,
|
||||
selected = eventSortOrder,
|
||||
onSelect = { state.updateEventSortOrder(it) },
|
||||
collapsed = collapsed,
|
||||
onToggleCollapse = { collapsedSections["notes"] = !collapsed },
|
||||
)
|
||||
}
|
||||
val displayNotes = textNotes.take(5)
|
||||
items(displayNotes, key = { "note-${it.id}" }) { event ->
|
||||
NotePreviewCard(event = event, onClick = { onNavigateToThread(event.id) })
|
||||
}
|
||||
if (textNotes.size > 5) {
|
||||
item(key = "notes-expand") {
|
||||
ExpandableSection(
|
||||
remaining = textNotes.drop(5),
|
||||
) { event ->
|
||||
NotePreviewCard(event = event, onClick = { onNavigateToThread(event.id) })
|
||||
if (!collapsed) {
|
||||
val displayNotes = textNotes.take(5)
|
||||
items(displayNotes, key = { "note-${it.id}" }) { event ->
|
||||
NotePreviewCard(event = event, onClick = { onNavigateToThread(event.id) })
|
||||
}
|
||||
if (textNotes.size > 5) {
|
||||
item(key = "notes-expand") {
|
||||
ExpandableSection(
|
||||
remaining = textNotes.drop(5),
|
||||
) { event ->
|
||||
NotePreviewCard(event = event, onClick = { onNavigateToThread(event.id) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,18 +180,30 @@ fun SearchResultsList(
|
||||
if (people.isNotEmpty() || textNotes.isNotEmpty()) {
|
||||
item(key = "divider-articles") { HorizontalDivider(Modifier.padding(vertical = 4.dp)) }
|
||||
}
|
||||
val collapsed = collapsedSections["articles"] == true
|
||||
stickyHeader(key = "header-articles") {
|
||||
SectionHeader("Articles", articles.size, Icons.Default.Article)
|
||||
SortableHeader(
|
||||
title = "Articles",
|
||||
count = articles.size,
|
||||
icon = Icons.Default.Article,
|
||||
options = SearchSortOrder.EVENT_OPTIONS,
|
||||
selected = eventSortOrder,
|
||||
onSelect = { state.updateEventSortOrder(it) },
|
||||
collapsed = collapsed,
|
||||
onToggleCollapse = { collapsedSections["articles"] = !collapsed },
|
||||
)
|
||||
}
|
||||
items(articles.take(5), key = { "article-${it.id}" }) { event ->
|
||||
NotePreviewCard(event = event, onClick = { onNavigateToThread(event.id) })
|
||||
}
|
||||
if (articles.size > 5) {
|
||||
item(key = "articles-expand") {
|
||||
ExpandableSection(
|
||||
remaining = articles.drop(5),
|
||||
) { event ->
|
||||
NotePreviewCard(event = event, onClick = { onNavigateToThread(event.id) })
|
||||
if (!collapsed) {
|
||||
items(articles.take(5), key = { "article-${it.id}" }) { event ->
|
||||
NotePreviewCard(event = event, onClick = { onNavigateToThread(event.id) })
|
||||
}
|
||||
if (articles.size > 5) {
|
||||
item(key = "articles-expand") {
|
||||
ExpandableSection(
|
||||
remaining = articles.drop(5),
|
||||
) { event ->
|
||||
NotePreviewCard(event = event, onClick = { onNavigateToThread(event.id) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,11 +212,23 @@ fun SearchResultsList(
|
||||
// Other section
|
||||
if (otherNotes.isNotEmpty()) {
|
||||
item(key = "divider-other") { HorizontalDivider(Modifier.padding(vertical = 4.dp)) }
|
||||
val collapsed = collapsedSections["other"] == true
|
||||
stickyHeader(key = "header-other") {
|
||||
SectionHeader("Other", otherNotes.size, Icons.Default.Forum)
|
||||
SortableHeader(
|
||||
title = "Other",
|
||||
count = otherNotes.size,
|
||||
icon = Icons.Default.Forum,
|
||||
options = SearchSortOrder.EVENT_OPTIONS,
|
||||
selected = eventSortOrder,
|
||||
onSelect = { state.updateEventSortOrder(it) },
|
||||
collapsed = collapsed,
|
||||
onToggleCollapse = { collapsedSections["other"] = !collapsed },
|
||||
)
|
||||
}
|
||||
items(otherNotes.take(5), key = { "other-${it.id}" }) { event ->
|
||||
NotePreviewCard(event = event, onClick = { onNavigateToThread(event.id) })
|
||||
if (!collapsed) {
|
||||
items(otherNotes.take(5), key = { "other-${it.id}" }) { event ->
|
||||
NotePreviewCard(event = event, onClick = { onNavigateToThread(event.id) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,31 +238,67 @@ fun SearchResultsList(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SectionHeader(
|
||||
private fun SortableHeader(
|
||||
title: String,
|
||||
count: Int,
|
||||
icon: ImageVector,
|
||||
options: List<SearchSortOrder>,
|
||||
selected: SearchSortOrder,
|
||||
onSelect: (SearchSortOrder) -> Unit,
|
||||
collapsed: Boolean = false,
|
||||
onToggleCollapse: () -> Unit = {},
|
||||
) {
|
||||
val chevronRotation by animateFloatAsState(if (collapsed) -90f else 0f)
|
||||
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
modifier = Modifier.clickable(onClick = onToggleCollapse).padding(vertical = 4.dp),
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.ExpandMore,
|
||||
contentDescription = if (collapsed) "Expand $title" else "Collapse $title",
|
||||
modifier = Modifier.size(18.dp).rotate(chevronRotation),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Icon(
|
||||
icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
modifier = Modifier.padding(start = 4.dp).size(18.dp),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Text(
|
||||
"$title ($count)",
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
)
|
||||
Spacer(Modifier.weight(1f))
|
||||
if (!collapsed) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
options.forEach { option ->
|
||||
FilterChip(
|
||||
selected = option == selected,
|
||||
onClick = { onSelect(option) },
|
||||
label = {
|
||||
Text(
|
||||
option.label,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
)
|
||||
},
|
||||
colors =
|
||||
FilterChipDefaults.filterChipColors(
|
||||
selectedContainerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
selectedLabelColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
modifier = Modifier.height(28.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user