diff --git a/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/demos/V2BottomSheetActivity.kt b/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/demos/V2BottomSheetActivity.kt index 062575e5..2f8e3c3c 100644 --- a/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/demos/V2BottomSheetActivity.kt +++ b/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/demos/V2BottomSheetActivity.kt @@ -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 { + 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 { + 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 + ) + ) +} \ No newline at end of file diff --git a/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/demos/V2ListItemActivity.kt b/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/demos/V2ListItemActivity.kt index 42583ee4..a0ff21f3 100644 --- a/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/demos/V2ListItemActivity.kt +++ b/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/demos/V2ListItemActivity.kt @@ -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), diff --git a/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/ControlTokens.kt b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/ControlTokens.kt index 7170f9f4..2e2cc27d 100644 --- a/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/ControlTokens.kt +++ b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/ControlTokens.kt @@ -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() } diff --git a/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/BottomSheetTokens.kt b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/BottomSheetTokens.kt index 50ba0b5d..577c22c8 100644 --- a/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/BottomSheetTokens.kt +++ b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/BottomSheetTokens.kt @@ -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) } \ No newline at end of file diff --git a/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/DividerTokens.kt b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/DividerTokens.kt new file mode 100644 index 00000000..d337f209 --- /dev/null +++ b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/DividerTokens.kt @@ -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 + ) + } +} \ No newline at end of file diff --git a/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/TabItemTokens.kt b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/TabItemTokens.kt new file mode 100644 index 00000000..b3b202ac --- /dev/null +++ b/fluentui_core/src/main/java/com/microsoft/fluentui/theme/token/controlTokens/TabItemTokens.kt @@ -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) + ) + } + } +} \ No newline at end of file diff --git a/fluentui_drawer/src/main/java/com/microsoft/fluentui/tokenized/bottomsheet/BottomSheet.kt b/fluentui_drawer/src/main/java/com/microsoft/fluentui/tokenized/bottomsheet/BottomSheet.kt index 5c5d240c..78bc4b15 100644 --- a/fluentui_drawer/src/main/java/com/microsoft/fluentui/tokenized/bottomsheet/BottomSheet.kt +++ b/fluentui_drawer/src/main/java/com/microsoft/fluentui/tokenized/bottomsheet/BottomSheet.kt @@ -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( diff --git a/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/contentBuilder/ListContentBuilder.kt b/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/contentBuilder/ListContentBuilder.kt new file mode 100644 index 00000000..2c81f49a --- /dev/null +++ b/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/contentBuilder/ListContentBuilder.kt @@ -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, + 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, + 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, + 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 = ArrayList() + + private fun add(contentData: ContentData) { + listOfContentData.add(contentData) + } + + fun addHorizontalList( + itemDataList: List, + header: String? = null, + fixedWidth: Boolean = false, + tabItemTokens: TabItemTokens? = null + ): ListContentBuilder { + add(HorizontalListContentData(itemDataList, header, fixedWidth, tabItemTokens)) + return this + } + + fun addVerticalList( + itemDataList: List, + header: String? = null, + listItemTokens: ListItemTokens? = null + ): ListContentBuilder { + add(VerticalListContentData(itemDataList, header, listItemTokens)) + return this + } + + fun addVerticalGrid( + itemDataList: List, + 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, + 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, + 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, + 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 + ) + } + } + } +} \ No newline at end of file diff --git a/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/divider/Divider.kt b/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/divider/Divider.kt new file mode 100644 index 00000000..a30773e9 --- /dev/null +++ b/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/divider/Divider.kt @@ -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())) + ) + } + } +} \ No newline at end of file diff --git a/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/listitem/ListItem.kt b/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/listitem/ListItem.kt index 1c8cc66a..b1693189 100644 --- a/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/listitem/ListItem.kt +++ b/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/listitem/ListItem.kt @@ -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() + } + } + } + } + } + } } \ No newline at end of file diff --git a/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/tabItem/TabItem.kt b/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/tabItem/TabItem.kt new file mode 100644 index 00000000..76927ab1 --- /dev/null +++ b/fluentui_listitem/src/main/java/com/microsoft/fluentui/tokenized/tabItem/TabItem.kt @@ -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 + ) + } + } + } + } +} \ No newline at end of file