ListContentBuilder (#289)
* Created controls: TabItem, Header, Divider. Implemented ItemListContentBuilder api to create horizontal list, vertical grid, vertical list * Renaming + some fix * Update Divider token + Fix accessibility in Header control + renaming * Optimized code * Added missing parameter for function
This commit is contained in:
Родитель
3c357ba000
Коммит
34e52d1aeb
|
@ -4,9 +4,12 @@ import android.content.Context
|
|||
import android.os.Bundle
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
@ -15,18 +18,25 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import com.microsoft.fluentui.persona.PersonaListView
|
||||
import com.microsoft.fluentui.theme.AppThemeController
|
||||
import com.microsoft.fluentui.theme.FluentTheme
|
||||
import com.microsoft.fluentui.theme.ThemeMode
|
||||
import com.microsoft.fluentui.theme.token.AliasTokens
|
||||
import com.microsoft.fluentui.theme.token.controlTokens.ButtonSize
|
||||
import com.microsoft.fluentui.theme.token.controlTokens.ButtonStyle
|
||||
import com.microsoft.fluentui.tokenized.bottomsheet.BottomSheet
|
||||
import com.microsoft.fluentui.tokenized.bottomsheet.BottomSheetState
|
||||
import com.microsoft.fluentui.tokenized.bottomsheet.BottomSheetValue
|
||||
import com.microsoft.fluentui.tokenized.bottomsheet.rememberBottomSheetState
|
||||
import com.microsoft.fluentui.tokenized.contentBuilder.ItemData
|
||||
import com.microsoft.fluentui.tokenized.contentBuilder.ListContentBuilder
|
||||
import com.microsoft.fluentui.tokenized.controls.Button
|
||||
import com.microsoft.fluentui.tokenized.controls.RadioButton
|
||||
import com.microsoft.fluentui.tokenized.controls.ToggleSwitch
|
||||
import com.microsoft.fluentui.util.activity
|
||||
import com.microsoft.fluentuidemo.DemoActivity
|
||||
|
@ -62,8 +72,6 @@ private fun CreateActivityUI() {
|
|||
|
||||
var peekHeightState by remember { mutableStateOf(110.dp) }
|
||||
|
||||
var toggleBottomSheetContent by remember { mutableStateOf(true) }
|
||||
|
||||
var hidden by remember { mutableStateOf(true) }
|
||||
|
||||
val bottomSheetState = rememberBottomSheetState(BottomSheetValue.Shown)
|
||||
|
@ -72,7 +80,34 @@ private fun CreateActivityUI() {
|
|||
|
||||
hidden = !bottomSheetState.isVisible
|
||||
|
||||
var sheetContentState by remember { mutableStateOf(content1) }
|
||||
val context = LocalContext.current
|
||||
val contentByListContentBuilder = ListContentBuilder()
|
||||
.addHorizontalList(getSingleLineList(context), "Default: Wrapped")
|
||||
.addDivider()
|
||||
.addHorizontalList(getSingleLineList(context), "Fixed width", fixedWidth = true)
|
||||
.addDivider()
|
||||
.addVerticalGrid(
|
||||
getSingleLineList(context),
|
||||
"Vertical Grid",
|
||||
3
|
||||
)
|
||||
.addDivider()
|
||||
.addVerticalGrid(
|
||||
getSingleLineList(context),
|
||||
"Vertical Grid: Equidistant",
|
||||
3,
|
||||
true
|
||||
)
|
||||
.addVerticalList(getDoubleLineList(context), "Double Line List")
|
||||
.addVerticalList(
|
||||
getSingleLineList(context),
|
||||
"Single Line List"
|
||||
)
|
||||
.getContent()
|
||||
var sheetContentState by remember { mutableStateOf(contentByListContentBuilder) }
|
||||
val content = listOf(0, 1, 2)
|
||||
val selectedOption = remember { mutableStateOf(content[0]) }
|
||||
|
||||
BottomSheet(
|
||||
sheetContent = sheetContentState,
|
||||
expandable = expandableState,
|
||||
|
@ -89,7 +124,7 @@ private fun CreateActivityUI() {
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
com.microsoft.fluentui.tokenized.controls.Button(
|
||||
Button(
|
||||
style = ButtonStyle.OutlinedButton,
|
||||
size = ButtonSize.Medium,
|
||||
text = "Show",
|
||||
|
@ -100,7 +135,7 @@ private fun CreateActivityUI() {
|
|||
}
|
||||
)
|
||||
|
||||
com.microsoft.fluentui.tokenized.controls.Button(
|
||||
Button(
|
||||
style = ButtonStyle.OutlinedButton,
|
||||
size = ButtonSize.Medium,
|
||||
text = "Jump Show",
|
||||
|
@ -128,7 +163,7 @@ private fun CreateActivityUI() {
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
com.microsoft.fluentui.tokenized.controls.Button(
|
||||
Button(
|
||||
style = ButtonStyle.OutlinedButton,
|
||||
size = ButtonSize.Medium,
|
||||
text = "Hide",
|
||||
|
@ -139,7 +174,7 @@ private fun CreateActivityUI() {
|
|||
}
|
||||
)
|
||||
|
||||
com.microsoft.fluentui.tokenized.controls.Button(
|
||||
Button(
|
||||
style = ButtonStyle.OutlinedButton,
|
||||
size = ButtonSize.Medium,
|
||||
text = "Expand",
|
||||
|
@ -207,14 +242,14 @@ private fun CreateActivityUI() {
|
|||
themeMode = ThemeMode.Auto
|
||||
)
|
||||
)
|
||||
com.microsoft.fluentui.tokenized.controls.Button(
|
||||
Button(
|
||||
style = ButtonStyle.Button,
|
||||
size = ButtonSize.Medium,
|
||||
text = "+ 8 dp",
|
||||
enabled = !hidden,
|
||||
onClick = { peekHeightState += 8.dp })
|
||||
|
||||
com.microsoft.fluentui.tokenized.controls.Button(
|
||||
Button(
|
||||
style = ButtonStyle.Button,
|
||||
size = ButtonSize.Medium,
|
||||
text = "- 8 dp",
|
||||
|
@ -222,21 +257,70 @@ private fun CreateActivityUI() {
|
|||
onClick = { peekHeightState -= 8.dp })
|
||||
}
|
||||
|
||||
com.microsoft.fluentui.tokenized.controls.Button(
|
||||
style = ButtonStyle.OutlinedButton,
|
||||
size = ButtonSize.Medium,
|
||||
text = "Toggle BottomSheet Content",
|
||||
onClick = {
|
||||
toggleBottomSheetContent = !toggleBottomSheetContent
|
||||
sheetContentState = if (toggleBottomSheetContent) {
|
||||
content1
|
||||
} else {
|
||||
content2
|
||||
}
|
||||
}
|
||||
)
|
||||
Row {
|
||||
Text(
|
||||
text = "Select SheetContent",
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.weight(1F),
|
||||
color = AppThemeController.aliasTokens.value!!.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
|
||||
themeMode = ThemeMode.Auto
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
com.microsoft.fluentui.tokenized.controls.Button(
|
||||
Row {
|
||||
Text(
|
||||
text = "From ItemListContentBuilder",
|
||||
modifier = Modifier.weight(1F),
|
||||
color = AppThemeController.aliasTokens.value!!.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
|
||||
themeMode = ThemeMode.Auto
|
||||
)
|
||||
)
|
||||
|
||||
RadioButton(
|
||||
selected = (selectedOption.value == content[0]),
|
||||
onClick = {
|
||||
selectedOption.value = content[0]
|
||||
sheetContentState = contentByListContentBuilder
|
||||
}
|
||||
)
|
||||
}
|
||||
Row {
|
||||
Text(
|
||||
text = "Using AndroidView",
|
||||
modifier = Modifier.weight(1F),
|
||||
color = AppThemeController.aliasTokens.value!!.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
|
||||
themeMode = ThemeMode.Auto
|
||||
)
|
||||
)
|
||||
|
||||
RadioButton(
|
||||
selected = (selectedOption.value == content[1]),
|
||||
onClick = {
|
||||
selectedOption.value = content[1]
|
||||
sheetContentState = content1(bottomSheetState)
|
||||
}
|
||||
)
|
||||
}
|
||||
Row {
|
||||
Text(
|
||||
text = "Compose Content",
|
||||
modifier = Modifier.weight(1F),
|
||||
color = AppThemeController.aliasTokens.value!!.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
|
||||
themeMode = ThemeMode.Auto
|
||||
)
|
||||
)
|
||||
|
||||
RadioButton(
|
||||
selected = (selectedOption.value == content[2]),
|
||||
onClick = {
|
||||
selectedOption.value = content[2]
|
||||
sheetContentState = content2(bottomSheetState)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Button(
|
||||
style = ButtonStyle.OutlinedButton,
|
||||
size = ButtonSize.Medium,
|
||||
enabled = !hidden,
|
||||
|
@ -283,18 +367,25 @@ private fun CreateActivityUI() {
|
|||
|
||||
)
|
||||
{
|
||||
Text(text = stringResource(R.string.large_scrollable_text))
|
||||
Text(
|
||||
text = context.resources.getString(R.string.large_scrollable_text),
|
||||
color = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
|
||||
themeMode = ThemeMode.Auto
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val content1: @Composable () -> Unit = {
|
||||
fun content1(bottomSheetState: BottomSheetState): @Composable () -> Unit = {
|
||||
lateinit var context: Context
|
||||
val scope = rememberCoroutineScope()
|
||||
val state = rememberScrollState()
|
||||
AndroidView(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
.verticalScroll(state),
|
||||
factory = {
|
||||
context = it
|
||||
val view = it.activity!!.layoutInflater.inflate(
|
||||
|
@ -305,21 +396,29 @@ val content1: @Composable () -> Unit = {
|
|||
(view as PersonaListView).personas = personaList
|
||||
view
|
||||
}
|
||||
) {}
|
||||
) {
|
||||
if (bottomSheetState.currentValue == BottomSheetValue.Shown) {
|
||||
scope.launch {
|
||||
state.animateScrollTo(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val content2: @Composable () -> Unit = {
|
||||
fun content2(bottomSheetState: BottomSheetState): @Composable () -> Unit = {
|
||||
val no = remember { mutableStateOf(0) }
|
||||
val lazyListState = rememberLazyListState()
|
||||
LazyColumn(
|
||||
state = lazyListState,
|
||||
horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
item {
|
||||
com.microsoft.fluentui.tokenized.controls.Button(
|
||||
Button(
|
||||
style = ButtonStyle.Button,
|
||||
size = ButtonSize.Medium,
|
||||
text = "Click to create random size list",
|
||||
onClick = { no.value = (20 * Math.random()).toInt() })
|
||||
onClick = { no.value = (40 * Math.random()).toInt() })
|
||||
}
|
||||
|
||||
repeat(no.value) {
|
||||
|
@ -329,5 +428,67 @@ val content2: @Composable () -> Unit = {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
LaunchedEffect(key1 = bottomSheetState.currentValue) {
|
||||
if (bottomSheetState.currentValue == BottomSheetValue.Shown) {
|
||||
scope.launch {
|
||||
lazyListState.animateScrollToItem(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getSingleLineList(context: Context): List<ItemData> {
|
||||
return arrayListOf(
|
||||
ItemData(icon = Icons.Outlined.Email, title = "Email", onClick = {}),
|
||||
ItemData(
|
||||
icon = Icons.Outlined.ArrowBack,
|
||||
title = context.resources.getString(R.string.bottom_sheet_item_reply_title),
|
||||
onClick = {}
|
||||
),
|
||||
ItemData(
|
||||
icon = Icons.Outlined.ArrowForward,
|
||||
title = context.resources.getString(R.string.bottom_sheet_item_forward_title),
|
||||
onClick = {},
|
||||
enabled = false
|
||||
),
|
||||
ItemData(icon = Icons.Outlined.Favorite, title = "Favorite", onClick = {}, enabled = false),
|
||||
ItemData(icon = Icons.Outlined.Info, title = "Long Info text", onClick = {}),
|
||||
ItemData(icon = Icons.Outlined.Menu, title = "Menu", onClick = {}),
|
||||
ItemData(icon = Icons.Outlined.Share, title = "Share", onClick = {}),
|
||||
ItemData(
|
||||
icon = Icons.Outlined.Delete,
|
||||
title = context.resources.getString(R.string.bottom_sheet_item_delete_title),
|
||||
onClick = {})
|
||||
)
|
||||
}
|
||||
|
||||
fun getDoubleLineList(context: Context): List<ItemData> {
|
||||
return arrayListOf(
|
||||
ItemData(
|
||||
icon = Icons.Outlined.Person,
|
||||
title = context.resources.getString(R.string.bottom_sheet_item_camera_title),
|
||||
subTitle = context.resources.getString(R.string.bottom_sheet_item_camera_subtitle),
|
||||
onClick = {}),
|
||||
ItemData(
|
||||
icon = Icons.Outlined.List,
|
||||
title = context.resources.getString(R.string.bottom_sheet_item_gallery_title),
|
||||
subTitle = context.resources.getString(R.string.bottom_sheet_item_gallery_subtitle),
|
||||
onClick = {},
|
||||
enabled = false
|
||||
),
|
||||
ItemData(
|
||||
icon = Icons.Outlined.Settings,
|
||||
title = context.resources.getString(R.string.bottom_sheet_item_manage_title),
|
||||
subTitle = context.resources.getString(R.string.bottom_sheet_item_manage_subtitle),
|
||||
onClick = {}),
|
||||
ItemData(
|
||||
icon = Icons.Outlined.Face,
|
||||
title = context.resources.getString(R.string.bottom_sheet_item_videos_title),
|
||||
subTitle = context.resources.getString(R.string.bottom_sheet_item_videos_subtitle),
|
||||
onClick = {},
|
||||
enabled = false
|
||||
)
|
||||
)
|
||||
}
|
|
@ -239,6 +239,26 @@ fun CreateListActivityUI() {
|
|||
}
|
||||
|
||||
}
|
||||
item {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 16.dp, top = 24.dp, bottom = 8.dp),
|
||||
text = "Header",
|
||||
color = Color(0xFF2886DE)
|
||||
)
|
||||
ListItem.Header(
|
||||
title = "Heading",
|
||||
accessoryTextTitle = "Action",
|
||||
accessoryTextOnClick = {},
|
||||
trailingAccessoryView = { RightViewThreeButton() },
|
||||
border = BorderType.Bottom
|
||||
)
|
||||
ListItem.Header(
|
||||
title = "Heading",
|
||||
accessoryTextTitle = "Action",
|
||||
accessoryTextOnClick = {},
|
||||
style = Subtle
|
||||
)
|
||||
}
|
||||
item {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 16.dp, top = 24.dp, bottom = 8.dp),
|
||||
|
|
|
@ -24,6 +24,7 @@ class ControlTokens {
|
|||
CircularProgressIndicator,
|
||||
ContextualCommandBar,
|
||||
Drawer,
|
||||
Divider,
|
||||
FloatingActionButton,
|
||||
LinearProgressIndicator,
|
||||
ListItem,
|
||||
|
@ -32,6 +33,7 @@ class ControlTokens {
|
|||
RadioButton,
|
||||
PillSwitch,
|
||||
PillTabs,
|
||||
TabItem,
|
||||
Shimmer,
|
||||
ToggleSwitch
|
||||
}
|
||||
|
@ -48,6 +50,7 @@ class ControlTokens {
|
|||
ControlType.CircularProgressIndicator -> CircularProgressIndicatorTokens()
|
||||
ControlType.ContextualCommandBar -> ContextualCommandBarTokens()
|
||||
ControlType.Drawer -> DrawerTokens()
|
||||
ControlType.Divider -> DividerTokens()
|
||||
ControlType.FloatingActionButton -> FABTokens()
|
||||
ControlType.LinearProgressIndicator -> LinearProgressIndicatorTokens()
|
||||
ControlType.ListItem -> ListItemTokens()
|
||||
|
@ -56,6 +59,7 @@ class ControlTokens {
|
|||
ControlType.RadioButton -> RadioButtonTokens()
|
||||
ControlType.PillSwitch -> PillSwitchTokens()
|
||||
ControlType.PillTabs -> PillTabsTokens()
|
||||
ControlType.TabItem -> TabItemTokens()
|
||||
ControlType.Shimmer -> ShimmerTokens()
|
||||
ControlType.ToggleSwitch -> ToggleSwitchTokens()
|
||||
}
|
||||
|
|
|
@ -6,10 +6,13 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.unit.Dp
|
||||
import com.microsoft.fluentui.theme.FluentTheme
|
||||
import com.microsoft.fluentui.theme.token.AliasTokens
|
||||
import com.microsoft.fluentui.theme.token.ControlInfo
|
||||
import com.microsoft.fluentui.theme.token.ControlToken
|
||||
import com.microsoft.fluentui.theme.token.GlobalTokens
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
class BottomSheetInfo() : ControlInfo
|
||||
|
||||
@Parcelize
|
||||
open class BottomSheetTokens : ControlToken, Parcelable {
|
||||
companion object {
|
||||
|
@ -17,30 +20,30 @@ open class BottomSheetTokens : ControlToken, Parcelable {
|
|||
}
|
||||
|
||||
@Composable
|
||||
open fun backgroundColor(): Color =
|
||||
open fun backgroundColor(bottomSheetInfo: BottomSheetInfo): Color =
|
||||
FluentTheme.aliasTokens.neutralBackgroundColor[AliasTokens.NeutralBackgroundColorTokens.Background2].value(
|
||||
themeMode = FluentTheme.themeMode
|
||||
)
|
||||
|
||||
@Composable
|
||||
open fun handleColor(): Color =
|
||||
open fun handleColor(bottomSheetInfo: BottomSheetInfo): Color =
|
||||
FluentTheme.aliasTokens.neutralStrokeColor[AliasTokens.NeutralStrokeColorTokens.Stroke1].value(
|
||||
themeMode = FluentTheme.themeMode
|
||||
)
|
||||
|
||||
@Composable
|
||||
open fun elevation(): Dp =
|
||||
open fun elevation(bottomSheetInfo: BottomSheetInfo): Dp =
|
||||
GlobalTokens.elevation(GlobalTokens.ShadowTokens.Shadow28)
|
||||
|
||||
@Composable
|
||||
open fun borderRadius(): Dp =
|
||||
open fun borderRadius(bottomSheetInfo: BottomSheetInfo): Dp =
|
||||
GlobalTokens.borderRadius(GlobalTokens.BorderRadiusTokens.XLarge)
|
||||
|
||||
@Composable
|
||||
open fun scrimColor(): Color =
|
||||
open fun scrimColor(bottomSheetInfo: BottomSheetInfo): Color =
|
||||
GlobalTokens.neutralColor(GlobalTokens.NeutralColorTokens.Black)
|
||||
|
||||
@Composable
|
||||
open fun scrimOpacity(): Float =
|
||||
open fun scrimOpacity(bottomSheetInfo: BottomSheetInfo): Float =
|
||||
GlobalTokens.opacity(GlobalTokens.OpacityTokens.Opacity32)
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.microsoft.fluentui.theme.token.controlTokens
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.microsoft.fluentui.theme.FluentTheme
|
||||
import com.microsoft.fluentui.theme.token.AliasTokens
|
||||
import com.microsoft.fluentui.theme.token.ControlInfo
|
||||
import com.microsoft.fluentui.theme.token.ControlToken
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
class DividerInfo : ControlInfo
|
||||
|
||||
@Parcelize
|
||||
open class DividerTokens : ControlToken, Parcelable {
|
||||
@Composable
|
||||
open fun background(dividerInfo: DividerInfo): Color {
|
||||
return FluentTheme.aliasTokens.neutralBackgroundColor[AliasTokens.NeutralBackgroundColorTokens.Background1].value(
|
||||
FluentTheme.themeMode
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
open fun dividerColor(dividerInfo: DividerInfo): Color {
|
||||
return FluentTheme.aliasTokens.neutralStrokeColor[AliasTokens.NeutralStrokeColorTokens.Stroke2].value(
|
||||
themeMode = FluentTheme.themeMode
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package com.microsoft.fluentui.theme.token.controlTokens
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.microsoft.fluentui.theme.FluentTheme
|
||||
import com.microsoft.fluentui.theme.ThemeMode
|
||||
import com.microsoft.fluentui.theme.token.*
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
enum class TabTextAlignment {
|
||||
VERTICAL,
|
||||
HORIZONTAL,
|
||||
NO_TEXT
|
||||
}
|
||||
|
||||
class TabItemInfo(
|
||||
val tabTextAlignment: TabTextAlignment = TabTextAlignment.VERTICAL,
|
||||
val fluentStyle: FluentStyle = FluentStyle.Neutral
|
||||
) : ControlInfo
|
||||
|
||||
@Parcelize
|
||||
open class TabItemTokens : ControlToken, Parcelable {
|
||||
companion object {
|
||||
const val Type: String = "TabItem"
|
||||
}
|
||||
|
||||
@Composable
|
||||
open fun width(tabItemInfo: TabItemInfo): Dp {
|
||||
return 64.dp
|
||||
}
|
||||
|
||||
@Composable
|
||||
open fun background(tabItemInfo: TabItemInfo): Color {
|
||||
return FluentTheme.aliasTokens.neutralBackgroundColor[AliasTokens.NeutralBackgroundColorTokens.Background1].value(
|
||||
FluentTheme.themeMode
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
open fun rippleColor(tabItemInfo: TabItemInfo): Color {
|
||||
return FluentColor(
|
||||
light = GlobalTokens.neutralColor(GlobalTokens.NeutralColorTokens.Black),
|
||||
dark = GlobalTokens.neutralColor(GlobalTokens.NeutralColorTokens.White)
|
||||
).value(
|
||||
FluentTheme.themeMode
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
open fun iconColor(tabItemInfo: TabItemInfo): StateColor {
|
||||
return when (tabItemInfo.fluentStyle) {
|
||||
FluentStyle.Neutral -> StateColor(
|
||||
rest = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground3].value(
|
||||
themeMode = FluentTheme.themeMode
|
||||
),
|
||||
pressed = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground3].value(
|
||||
themeMode = FluentTheme.themeMode
|
||||
),
|
||||
disabled = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.ForegroundDisable1].value(
|
||||
themeMode = FluentTheme.themeMode
|
||||
)
|
||||
)
|
||||
|
||||
FluentStyle.Brand -> StateColor(
|
||||
rest = FluentColor(
|
||||
light = FluentTheme.aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForeground1].value(
|
||||
ThemeMode.Light
|
||||
),
|
||||
dark = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
|
||||
ThemeMode.Dark
|
||||
)
|
||||
).value(FluentTheme.themeMode),
|
||||
pressed = FluentColor(
|
||||
light = FluentTheme.aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForeground1Pressed].value(
|
||||
ThemeMode.Light
|
||||
),
|
||||
dark = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
|
||||
ThemeMode.Dark
|
||||
)
|
||||
).value(FluentTheme.themeMode),
|
||||
disabled = FluentColor(
|
||||
light = FluentTheme.aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForegroundDisabled2].value(
|
||||
ThemeMode.Light
|
||||
),
|
||||
dark = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.ForegroundDisable1].value(
|
||||
ThemeMode.Dark
|
||||
)
|
||||
).value(FluentTheme.themeMode)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
open fun textColor(tabItemInfo: TabItemInfo): StateColor {
|
||||
return when (tabItemInfo.fluentStyle) {
|
||||
FluentStyle.Neutral -> StateColor(
|
||||
rest = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground2].value(
|
||||
themeMode = FluentTheme.themeMode
|
||||
),
|
||||
pressed = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
|
||||
themeMode = FluentTheme.themeMode
|
||||
),
|
||||
disabled = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.ForegroundDisable1].value(
|
||||
themeMode = FluentTheme.themeMode
|
||||
)
|
||||
)
|
||||
|
||||
FluentStyle.Brand -> StateColor(
|
||||
rest = FluentColor(
|
||||
light = FluentTheme.aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForeground1].value(
|
||||
ThemeMode.Light
|
||||
),
|
||||
dark = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
|
||||
ThemeMode.Dark
|
||||
)
|
||||
).value(FluentTheme.themeMode),
|
||||
pressed = FluentColor(
|
||||
light = FluentTheme.aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForeground1Pressed].value(
|
||||
ThemeMode.Light
|
||||
),
|
||||
dark = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.Foreground1].value(
|
||||
ThemeMode.Dark
|
||||
)
|
||||
).value(FluentTheme.themeMode),
|
||||
disabled = FluentColor(
|
||||
light = FluentTheme.aliasTokens.brandForegroundColor[AliasTokens.BrandForegroundColorTokens.BrandForegroundDisabled2].value(
|
||||
ThemeMode.Light
|
||||
),
|
||||
dark = FluentTheme.aliasTokens.neutralForegroundColor[AliasTokens.NeutralForegroundColorTokens.ForegroundDisable1].value(
|
||||
ThemeMode.Dark
|
||||
)
|
||||
).value(FluentTheme.themeMode)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,6 +37,7 @@ import com.microsoft.fluentui.compose.SwipeableDefaults
|
|||
import com.microsoft.fluentui.compose.SwipeableState
|
||||
import com.microsoft.fluentui.theme.FluentTheme
|
||||
import com.microsoft.fluentui.theme.token.ControlTokens
|
||||
import com.microsoft.fluentui.theme.token.controlTokens.BottomSheetInfo
|
||||
import com.microsoft.fluentui.theme.token.controlTokens.BottomSheetTokens
|
||||
import com.microsoft.fluentui.tokenized.calculateFraction
|
||||
import com.microsoft.fluentui.util.dpToPx
|
||||
|
@ -183,12 +184,18 @@ private const val BOTTOMSHEET_SCRIM_TAG = "BottomSheet Scrim"
|
|||
private const val BottomSheetOpenFraction = 0.5f
|
||||
|
||||
internal val LocalBottomSheetTokens = compositionLocalOf { BottomSheetTokens() }
|
||||
internal val LocalBottomSheetInfo = compositionLocalOf { BottomSheetInfo() }
|
||||
|
||||
@Composable
|
||||
private fun getDrawerTokens(): BottomSheetTokens {
|
||||
return LocalBottomSheetTokens.current
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getBottomSheetInfo(): BottomSheetInfo {
|
||||
return LocalBottomSheetInfo.current
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Bottom sheets present a set of choices while blocking interaction with the rest of the
|
||||
|
@ -229,18 +236,22 @@ fun BottomSheet(
|
|||
val tokens = bottomSheetTokens
|
||||
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.BottomSheet] as BottomSheetTokens
|
||||
|
||||
CompositionLocalProvider(LocalBottomSheetTokens provides tokens) {
|
||||
CompositionLocalProvider(
|
||||
LocalBottomSheetTokens provides tokens,
|
||||
LocalBottomSheetInfo provides BottomSheetInfo()
|
||||
) {
|
||||
|
||||
val sheetShape: Shape = RoundedCornerShape(
|
||||
topStart = getDrawerTokens().borderRadius(),
|
||||
topEnd = getDrawerTokens().borderRadius()
|
||||
topStart = getDrawerTokens().borderRadius(getBottomSheetInfo()),
|
||||
topEnd = getDrawerTokens().borderRadius(getBottomSheetInfo())
|
||||
)
|
||||
val sheetElevation: Dp = getDrawerTokens().elevation()
|
||||
val sheetBackgroundColor: Color = getDrawerTokens().backgroundColor()
|
||||
val sheetElevation: Dp = getDrawerTokens().elevation(getBottomSheetInfo())
|
||||
val sheetBackgroundColor: Color = getDrawerTokens().backgroundColor(getBottomSheetInfo())
|
||||
val sheetContentColor: Color = Color.Transparent
|
||||
val sheetHandleColor: Color = getDrawerTokens().handleColor()
|
||||
val scrimOpacity: Float = getDrawerTokens().scrimOpacity()
|
||||
val scrimColor: Color = getDrawerTokens().scrimColor().copy(alpha = scrimOpacity)
|
||||
val sheetHandleColor: Color = getDrawerTokens().handleColor(getBottomSheetInfo())
|
||||
val scrimOpacity: Float = getDrawerTokens().scrimOpacity(getBottomSheetInfo())
|
||||
val scrimColor: Color =
|
||||
getDrawerTokens().scrimColor(getBottomSheetInfo()).copy(alpha = scrimOpacity)
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
|
@ -458,8 +469,6 @@ private fun Modifier.sheetHeight(expandable: Boolean, fullHeight: Float, peekHei
|
|||
return this.then(modifier)
|
||||
}
|
||||
|
||||
|
||||
|
||||
//TODO : Revisit Scrim usage across module to check single scrim implementation across module.
|
||||
@Composable
|
||||
private fun Scrim(
|
||||
|
|
|
@ -0,0 +1,333 @@
|
|||
package com.microsoft.fluentui.tokenized.contentBuilder
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.onFocusEvent
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.microsoft.fluentui.theme.FluentTheme
|
||||
import com.microsoft.fluentui.theme.token.ControlTokens
|
||||
import com.microsoft.fluentui.theme.token.FluentStyle
|
||||
import com.microsoft.fluentui.theme.token.controlTokens.*
|
||||
import com.microsoft.fluentui.tokenized.divider.Divider
|
||||
import com.microsoft.fluentui.tokenized.listitem.ListItem
|
||||
import com.microsoft.fluentui.tokenized.tabItem.TabItem
|
||||
import kotlinx.coroutines.launch
|
||||
import java.lang.Integer.max
|
||||
import java.lang.Integer.min
|
||||
import kotlin.math.ceil
|
||||
|
||||
data class ItemData(
|
||||
var title: String,
|
||||
var subTitle: String? = null,
|
||||
var enabled: Boolean = true,
|
||||
var onClick: () -> Unit,
|
||||
var accessory: @Composable (() -> Unit)? = null,
|
||||
var icon: ImageVector? = null
|
||||
)
|
||||
|
||||
// marker interface
|
||||
internal interface ContentData
|
||||
|
||||
// data class for list of Items to be placed horizontally
|
||||
internal data class HorizontalListContentData(
|
||||
val itemDataList: List<ItemData>,
|
||||
val header: String? = null,
|
||||
val fixedWidth: Boolean = false,
|
||||
val tabItemTokens: TabItemTokens? = null
|
||||
) : ContentData
|
||||
|
||||
// data class for list of Items to be placed vertically
|
||||
internal data class VerticalListContentData(
|
||||
val itemDataList: List<ItemData>,
|
||||
val header: String? = null,
|
||||
val listItemTokens: ListItemTokens? = null
|
||||
) : ContentData
|
||||
|
||||
// data class for list of Items to be placed in vertical grid
|
||||
internal data class VerticalGridContentData(
|
||||
val itemDataList: List<ItemData>,
|
||||
val header: String? = null,
|
||||
val maxItemInRow: Int,
|
||||
val equidistant: Boolean = false,
|
||||
val tabItemTokens: TabItemTokens? = null
|
||||
) : ContentData
|
||||
|
||||
//data class for divider
|
||||
internal data class DividerContentData(val heightDp: Dp, val dividerToken: DividerTokens?) :
|
||||
ContentData
|
||||
|
||||
/*
|
||||
* Builder to create list of list (vertical, horizontal), grid.
|
||||
* */
|
||||
class ListContentBuilder {
|
||||
private val listOfContentData: ArrayList<ContentData> = ArrayList()
|
||||
|
||||
private fun add(contentData: ContentData) {
|
||||
listOfContentData.add(contentData)
|
||||
}
|
||||
|
||||
fun addHorizontalList(
|
||||
itemDataList: List<ItemData>,
|
||||
header: String? = null,
|
||||
fixedWidth: Boolean = false,
|
||||
tabItemTokens: TabItemTokens? = null
|
||||
): ListContentBuilder {
|
||||
add(HorizontalListContentData(itemDataList, header, fixedWidth, tabItemTokens))
|
||||
return this
|
||||
}
|
||||
|
||||
fun addVerticalList(
|
||||
itemDataList: List<ItemData>,
|
||||
header: String? = null,
|
||||
listItemTokens: ListItemTokens? = null
|
||||
): ListContentBuilder {
|
||||
add(VerticalListContentData(itemDataList, header, listItemTokens))
|
||||
return this
|
||||
}
|
||||
|
||||
fun addVerticalGrid(
|
||||
itemDataList: List<ItemData>,
|
||||
header: String? = null,
|
||||
maxItemInRow: Int = 4,
|
||||
equidistant: Boolean = false,
|
||||
tabItemTokens: TabItemTokens? = null
|
||||
): ListContentBuilder {
|
||||
add(VerticalGridContentData(itemDataList, header, maxItemInRow, equidistant, tabItemTokens))
|
||||
return this
|
||||
}
|
||||
|
||||
fun addDivider(
|
||||
heightDp: Dp = 1.dp,
|
||||
dividerToken: DividerTokens? = null
|
||||
): ListContentBuilder {
|
||||
add(DividerContentData(heightDp, dividerToken))
|
||||
return this
|
||||
}
|
||||
|
||||
fun getContent(): @Composable () -> Unit {
|
||||
//TODO As per the thread https://issuetracker.google.com/issues/184670295#comment34 focus
|
||||
// navigation issues should resolve with compose version 1.3.0.
|
||||
return {
|
||||
LazyColumn {
|
||||
for (contentData in listOfContentData) {
|
||||
when (contentData) {
|
||||
is HorizontalListContentData -> createHorizontalList(
|
||||
contentData.itemDataList,
|
||||
contentData.header,
|
||||
contentData.fixedWidth,
|
||||
contentData.tabItemTokens
|
||||
)()
|
||||
|
||||
is VerticalListContentData -> createVerticalList(
|
||||
contentData.itemDataList,
|
||||
contentData.header,
|
||||
contentData.listItemTokens
|
||||
)()
|
||||
|
||||
is VerticalGridContentData -> createVerticalGrid(
|
||||
contentData.itemDataList,
|
||||
contentData.header,
|
||||
contentData.maxItemInRow,
|
||||
contentData.equidistant,
|
||||
contentData.tabItemTokens
|
||||
)()
|
||||
|
||||
is DividerContentData -> createDivider(
|
||||
contentData.heightDp,
|
||||
contentData.dividerToken
|
||||
)()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createVerticalGrid(
|
||||
itemDataList: List<ItemData>,
|
||||
header: String?,
|
||||
maxItemsInRow: Int,
|
||||
equidistant: Boolean,
|
||||
tabItemTokens: TabItemTokens? = null
|
||||
): LazyListScope.() -> Unit {
|
||||
return {
|
||||
if (header != null) {
|
||||
item {
|
||||
ListItem.Header(title = header)
|
||||
}
|
||||
}
|
||||
val size = itemDataList.size
|
||||
val itemsInRow = min(size, maxItemsInRow)
|
||||
|
||||
items(ceil(size * 1.0 / itemsInRow).toInt()) { row ->
|
||||
val token =
|
||||
tabItemTokens
|
||||
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.TabItem] as TabItemTokens
|
||||
Layout(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
token.background(
|
||||
TabItemInfo(
|
||||
TabTextAlignment.VERTICAL,
|
||||
FluentStyle.Brand
|
||||
)
|
||||
)
|
||||
)
|
||||
.padding(start = 16.dp, end = 16.dp),
|
||||
content = {
|
||||
var col = 0
|
||||
val widthRatio = if ((row + 1) * itemsInRow <= size || !equidistant)
|
||||
1.0f / itemsInRow
|
||||
else
|
||||
1.0f / min(itemsInRow, (size - (row * itemsInRow)))
|
||||
while (col < itemsInRow && (row * itemsInRow + col) < size) {
|
||||
TabItem(
|
||||
title = itemDataList[row * itemsInRow + col].title,
|
||||
enabled = itemDataList[row * itemsInRow + col].enabled,
|
||||
onClick = itemDataList[row * itemsInRow + col].onClick,
|
||||
accessory = itemDataList[row * itemsInRow + col].accessory,
|
||||
icon = itemDataList[row * itemsInRow + col].icon,
|
||||
modifier = Modifier.fillMaxWidth(widthRatio),
|
||||
tabItemTokens = tabItemTokens
|
||||
)
|
||||
col++
|
||||
}
|
||||
}) { measurables, constraints ->
|
||||
val count = measurables.size
|
||||
val placeables = measurables.map { measurable ->
|
||||
measurable.measure(constraints)
|
||||
}
|
||||
var layoutHeight = 0
|
||||
|
||||
placeables.forEach {
|
||||
layoutHeight = layoutHeight.coerceAtLeast(it.height)
|
||||
}
|
||||
|
||||
layout(constraints.maxWidth, layoutHeight) {
|
||||
var xPosition = 0
|
||||
val width = if (equidistant)
|
||||
constraints.maxWidth / count
|
||||
else
|
||||
constraints.maxWidth / maxItemsInRow
|
||||
|
||||
placeables.forEach { placeable ->
|
||||
placeable.placeRelative(y = 0, x = xPosition)
|
||||
if (placeable != placeables.last())
|
||||
xPosition += width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createHorizontalList(
|
||||
itemDataList: List<ItemData>,
|
||||
header: String?,
|
||||
fixedWidth: Boolean = false,
|
||||
tabItemTokens: TabItemTokens? = null
|
||||
): LazyListScope.() -> Unit {
|
||||
return {
|
||||
if (header != null) {
|
||||
item {
|
||||
ListItem.Header(title = header)
|
||||
}
|
||||
}
|
||||
item {
|
||||
val rowScope = rememberCoroutineScope()
|
||||
val rowLazyListState = rememberLazyListState()
|
||||
val token =
|
||||
tabItemTokens
|
||||
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.TabItem] as TabItemTokens
|
||||
LazyRow(
|
||||
state = rowLazyListState, modifier = Modifier
|
||||
.background(
|
||||
token.background(
|
||||
TabItemInfo(
|
||||
TabTextAlignment.VERTICAL,
|
||||
FluentStyle.Brand
|
||||
)
|
||||
)
|
||||
)
|
||||
.padding(start = 16.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
itemsIndexed(itemDataList) { index, item ->
|
||||
TabItem(
|
||||
title = item.title,
|
||||
enabled = item.enabled,
|
||||
fixedWidth = fixedWidth,
|
||||
onClick = item.onClick,
|
||||
accessory = item.accessory,
|
||||
icon = item.icon,
|
||||
modifier = Modifier
|
||||
.onFocusEvent { focusState ->
|
||||
if (focusState.isFocused) {
|
||||
rowScope.launch {
|
||||
rowLazyListState.animateScrollToItem(
|
||||
max(0, index - 2)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
tabItemTokens = tabItemTokens
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createVerticalList(
|
||||
itemDataList: List<ItemData>,
|
||||
header: String?,
|
||||
listItemTokens: ListItemTokens? = null
|
||||
): LazyListScope.() -> Unit {
|
||||
return {
|
||||
if (header != null) {
|
||||
item {
|
||||
ListItem.Header(title = header)
|
||||
}
|
||||
}
|
||||
|
||||
items(itemDataList) { item ->
|
||||
val token = listItemTokens
|
||||
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.ListItem] as ListItemTokens
|
||||
ListItem.Item(
|
||||
text = item.title,
|
||||
subText = item.subTitle,
|
||||
leadingAccessoryView = {
|
||||
if (item.icon != null) {
|
||||
Icon(item.icon!!, null, tint = token.iconColor().rest)
|
||||
}
|
||||
},
|
||||
trailingAccessoryView = item.accessory,
|
||||
enabled = item.enabled,
|
||||
onClick = item.onClick,
|
||||
listItemTokens = listItemTokens
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDivider(
|
||||
heightDp: Dp,
|
||||
dividerToken: DividerTokens?
|
||||
): LazyListScope.() -> Unit {
|
||||
return {
|
||||
item {
|
||||
Divider(
|
||||
heightDp, dividerToken
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package com.microsoft.fluentui.tokenized.divider
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.microsoft.fluentui.theme.FluentTheme
|
||||
import com.microsoft.fluentui.theme.token.ControlTokens
|
||||
import com.microsoft.fluentui.theme.token.controlTokens.DividerInfo
|
||||
import com.microsoft.fluentui.theme.token.controlTokens.DividerTokens
|
||||
|
||||
val LocalDividerTokens = compositionLocalOf { DividerTokens() }
|
||||
val LocalDividerInfo = compositionLocalOf { DividerInfo() }
|
||||
|
||||
@Composable
|
||||
fun getDividerTokens(): DividerTokens {
|
||||
return LocalDividerTokens.current
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun getDividerInfo(): DividerInfo {
|
||||
return LocalDividerInfo.current
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Divider(
|
||||
height: Dp = 1.dp,
|
||||
dividerToken: DividerTokens? = null
|
||||
) {
|
||||
val token =
|
||||
dividerToken
|
||||
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.Divider] as DividerTokens
|
||||
CompositionLocalProvider(
|
||||
LocalDividerTokens provides token,
|
||||
LocalDividerInfo provides DividerInfo()
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.background(getDividerTokens().background(dividerInfo = getDividerInfo()))
|
||||
.focusable(false)
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.requiredHeight(height)
|
||||
.background(getDividerTokens().dividerColor(dividerInfo = getDividerInfo()))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,11 +4,11 @@ import androidx.compose.animation.*
|
|||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.InlineTextContent
|
||||
import androidx.compose.foundation.text.appendInlineContent
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
|
@ -24,7 +24,7 @@ import androidx.compose.ui.geometry.Offset
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.*
|
||||
import androidx.compose.ui.text.*
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
@ -404,7 +404,7 @@ object ListItem {
|
|||
* @param content Composable content to appear or disappear on clicking the list item
|
||||
*
|
||||
*/
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
|
||||
@Composable
|
||||
fun SectionHeader(
|
||||
title: String,
|
||||
|
@ -621,10 +621,13 @@ object ListItem {
|
|||
interactionSource = interactionSource
|
||||
)
|
||||
val horizontalPadding = getListItemTokens().padding(Medium)
|
||||
val leadPadding = if(leadingAccessoryView == null){
|
||||
val leadPadding = if (leadingAccessoryView == null) {
|
||||
PaddingValues(start = horizontalPadding, end = horizontalPadding)
|
||||
}else {
|
||||
PaddingValues(start = getListItemTokens().padding(size = GlobalTokens.SpacingTokens.None), end = horizontalPadding)
|
||||
} else {
|
||||
PaddingValues(
|
||||
start = getListItemTokens().padding(size = GlobalTokens.SpacingTokens.None),
|
||||
end = horizontalPadding
|
||||
)
|
||||
}
|
||||
val borderSize = getListItemTokens().borderSize().value
|
||||
val borderInsetToPx =
|
||||
|
@ -698,4 +701,135 @@ object ListItem {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Header. Headers are list tiles that delineates heading of a list or grid list
|
||||
*
|
||||
* @param modifier Optional modifier for List item.
|
||||
* @param title Section header title.
|
||||
* @param titleMaxLines Optional max visible lines for title.
|
||||
* @param accessoryTextTitle Optional accessory text.
|
||||
* @param accessoryTextOnClick Optional onClick action for accessory text.
|
||||
* @param enabled Optional enable/disable List item
|
||||
* @param style [SectionHeaderStyle] Section header style.
|
||||
* @param border [BorderType] Optional border for the list item.
|
||||
* @param borderInset [BorderInset] Optional borderInset for list item.
|
||||
* @param listItemTokens Optional list item tokens for list item appearance.If not provided then list tokens will be picked from [AppThemeController]
|
||||
* @param trailingAccessoryView Optional composable trailing accessory view.
|
||||
*
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun Header(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
titleMaxLines: Int = 1,
|
||||
accessoryTextTitle: String? = null,
|
||||
accessoryTextOnClick: (() -> Unit)? = null,
|
||||
enabled: Boolean = true,
|
||||
style: SectionHeaderStyle = SectionHeaderStyle.Standard,
|
||||
border: BorderType = NoBorder,
|
||||
borderInset: BorderInset = None,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
trailingAccessoryView: (@Composable () -> Unit)? = null,
|
||||
listItemTokens: ListItemTokens? = null
|
||||
) {
|
||||
val token = listItemTokens
|
||||
?: FluentTheme.controlTokens.tokens[ControlType.ListItem] as ListItemTokens
|
||||
CompositionLocalProvider(LocalListItemTokens provides token) {
|
||||
|
||||
val backgroundColor = getColorByState(
|
||||
stateData = getListItemTokens().backgroundColor(),
|
||||
enabled = true,
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
val cellHeight = getListItemTokens().cellHeight(listItemType = OneLine)
|
||||
val textSize =
|
||||
getListItemTokens().textSize(textType = ListTextType.Text, style = style)
|
||||
val actionTextSize =
|
||||
getListItemTokens().textSize(textType = ListTextType.ActionText, style = style)
|
||||
val textColor = getColorByState(
|
||||
stateData = getListItemTokens().textColor(
|
||||
textType = ListTextType.Text
|
||||
),
|
||||
enabled = enabled,
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
val actionTextColor =
|
||||
getColorByState(
|
||||
stateData = getListItemTokens().textColor(
|
||||
textType = ListTextType.ActionText
|
||||
),
|
||||
enabled = enabled,
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
val horizontalPadding = getListItemTokens().padding(Medium)
|
||||
val verticalPadding = getListItemTokens().padding(size = XSmall)
|
||||
val borderSize = getListItemTokens().borderSize().value
|
||||
val borderInsetToPx =
|
||||
with(LocalDensity.current) {
|
||||
getListItemTokens().borderInset(inset = borderInset).toPx()
|
||||
}
|
||||
val borderColor = getColorByState(
|
||||
stateData = getListItemTokens().borderColor(),
|
||||
enabled = enabled,
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = cellHeight)
|
||||
.background(backgroundColor)
|
||||
.borderModifier(border, borderColor, borderSize, borderInsetToPx)
|
||||
.focusable(false)
|
||||
) {
|
||||
Row(
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = cellHeight)
|
||||
.background(backgroundColor)
|
||||
.focusable(true),
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = horizontalPadding,
|
||||
end = horizontalPadding,
|
||||
bottom = verticalPadding
|
||||
)
|
||||
.weight(1f),
|
||||
fontSize = textSize.fontSize.size,
|
||||
fontWeight = textSize.weight,
|
||||
color = textColor,
|
||||
maxLines = titleMaxLines,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
if (accessoryTextTitle != null) {
|
||||
Text(text = accessoryTextTitle,
|
||||
modifier
|
||||
.padding(end = horizontalPadding, bottom = verticalPadding)
|
||||
.clickable(
|
||||
role = Role.Button,
|
||||
onClick = accessoryTextOnClick ?: {}
|
||||
),
|
||||
color = actionTextColor,
|
||||
fontSize = actionTextSize.fontSize.size,
|
||||
fontWeight = actionTextSize.weight)
|
||||
}
|
||||
if (trailingAccessoryView != null) {
|
||||
Box(
|
||||
Modifier.padding(end = horizontalPadding, bottom = verticalPadding),
|
||||
contentAlignment = Alignment.BottomStart
|
||||
) {
|
||||
trailingAccessoryView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
package com.microsoft.fluentui.tokenized.tabItem
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.microsoft.fluentui.theme.FluentTheme
|
||||
import com.microsoft.fluentui.theme.token.ControlTokens
|
||||
import com.microsoft.fluentui.theme.token.FluentStyle
|
||||
import com.microsoft.fluentui.theme.token.controlTokens.TabItemInfo
|
||||
import com.microsoft.fluentui.theme.token.controlTokens.TabItemTokens
|
||||
import com.microsoft.fluentui.theme.token.controlTokens.TabTextAlignment
|
||||
import com.microsoft.fluentui.tokenized.listitem.getColorByState
|
||||
|
||||
val LocalTabItemTokens = compositionLocalOf { TabItemTokens() }
|
||||
val LocalTabItemInfo =
|
||||
compositionLocalOf { TabItemInfo(TabTextAlignment.VERTICAL, FluentStyle.Neutral) }
|
||||
|
||||
@Composable
|
||||
fun getTabItemTokens(): TabItemTokens {
|
||||
return LocalTabItemTokens.current
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun getTabItemInfo(): TabItemInfo {
|
||||
return LocalTabItemInfo.current
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun TabItem(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
icon: ImageVector? = null,
|
||||
style: FluentStyle = FluentStyle.Neutral,
|
||||
textAlignment: TabTextAlignment = TabTextAlignment.VERTICAL,
|
||||
enabled: Boolean = true,
|
||||
fixedWidth: Boolean = false,
|
||||
onClick: () -> Unit,
|
||||
accessory: (@Composable () -> Unit)?,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
tabItemTokens: TabItemTokens? = null
|
||||
) {
|
||||
val token =
|
||||
tabItemTokens
|
||||
?: FluentTheme.controlTokens.tokens[ControlTokens.ControlType.TabItem] as TabItemTokens
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalTabItemTokens provides token,
|
||||
LocalTabItemInfo provides TabItemInfo(textAlignment, style)
|
||||
) {
|
||||
val textColor = getColorByState(
|
||||
stateData = getTabItemTokens().textColor(tabItemInfo = getTabItemInfo()),
|
||||
enabled = enabled,
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
val iconColor = getColorByState(
|
||||
stateData = getTabItemTokens().iconColor(tabItemInfo = getTabItemInfo()),
|
||||
enabled = enabled,
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
|
||||
val backgroundColor = getTabItemTokens().background(tabItemInfo = getTabItemInfo())
|
||||
val rippleColor = getTabItemTokens().rippleColor(tabItemInfo = getTabItemInfo())
|
||||
|
||||
val clickableModifier = Modifier
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(color = rippleColor),
|
||||
onClickLabel = null,
|
||||
enabled = enabled,
|
||||
onClick = onClick,
|
||||
role = Role.Tab
|
||||
)
|
||||
|
||||
val widthModifier = if (fixedWidth) {
|
||||
Modifier.width(getTabItemTokens().width(tabItemInfo = getTabItemInfo()))
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
val iconContent: @Composable () -> Unit = {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (icon != null) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
modifier = Modifier.height(if (textAlignment == TabTextAlignment.NO_TEXT) 28.dp else 24.dp),
|
||||
contentDescription = if (textAlignment == TabTextAlignment.NO_TEXT) title else null,
|
||||
tint = iconColor
|
||||
)
|
||||
}
|
||||
if (accessory != null) {
|
||||
accessory()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (textAlignment == TabTextAlignment.HORIZONTAL) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier
|
||||
.then(clickableModifier)
|
||||
.background(backgroundColor)
|
||||
.padding(top = 8.dp, start = 4.dp, bottom = 4.dp, end = 8.dp)
|
||||
.then(widthModifier)
|
||||
) {
|
||||
iconContent()
|
||||
Spacer(modifier = Modifier.width(2.dp))
|
||||
Text(
|
||||
text = title,
|
||||
color = textColor,
|
||||
textAlign = TextAlign.Justify
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = modifier
|
||||
.then(clickableModifier)
|
||||
.background(backgroundColor)
|
||||
.padding(top = 8.dp, start = 8.dp, bottom = 4.dp, end = 8.dp)
|
||||
.then(widthModifier)
|
||||
) {
|
||||
iconContent()
|
||||
if (textAlignment == TabTextAlignment.VERTICAL) {
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = title,
|
||||
color = textColor,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче