From 716ab9d04360ece02bc692792bbe2f951020ff9c Mon Sep 17 00:00:00 2001 From: sanlorng Date: Mon, 2 Dec 2024 21:49:14 +0800 Subject: [PATCH 1/4] [gallery] Rename DateTimeScreen to CalendarScreen Renamed the `DateTimeScreen` to `CalendarScreen` and updated the corresponding composable function, component description, source code reference, and gallery path. --- .../{DateTimeScreen.kt => CalendarScreen.kt} | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) rename gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/datetime/{DateTimeScreen.kt => CalendarScreen.kt} (65%) diff --git a/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/datetime/DateTimeScreen.kt b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/datetime/CalendarScreen.kt similarity index 65% rename from gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/datetime/DateTimeScreen.kt rename to gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/datetime/CalendarScreen.kt index 985bffa5..7eb5d704 100644 --- a/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/datetime/DateTimeScreen.kt +++ b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/datetime/CalendarScreen.kt @@ -8,20 +8,25 @@ import com.konyaco.fluent.component.CalendarDatePicker import com.konyaco.fluent.component.CalendarView import com.konyaco.fluent.gallery.annotation.Component import com.konyaco.fluent.gallery.annotation.Sample +import com.konyaco.fluent.gallery.component.ComponentPagePath import com.konyaco.fluent.gallery.component.GalleryPage +import com.konyaco.fluent.source.generated.FluentSourceFile -@Component +@Component(description = "A control that presents a calendar for a user to " + + "choose a date from.") @Composable -fun DateTimeScreen() { +fun CalendarScreen() { GalleryPage( - title = "DateTime", + title = "Calendar", description = "Lets users pick a date value using a calendar.", + componentPath = FluentSourceFile.CalendarView, + galleryPath = ComponentPagePath.CalendarScreen ) { Section( title = "A CalendarDatePicker", - sourceCode = sourceCodeOfDatePickerSample + sourceCode = sourceCodeOfCalendarDatePickerSample ) { - DatePickerSample() + CalendarDatePickerSample() } Section( @@ -35,7 +40,7 @@ fun DateTimeScreen() { @Sample @Composable -private fun DatePickerSample() { +private fun CalendarDatePickerSample() { CalendarDatePicker(onChoose = {}) } From 0e5285d841422f184ee4afeb091b9f09b64da33b Mon Sep 17 00:00:00 2001 From: sanlorng Date: Mon, 2 Dec 2024 22:02:43 +0800 Subject: [PATCH 2/4] [gallery] feat: Add Flyout component This commit also includes a basic usage sample demonstrating the flyout's functionality with a button trigger and content. --- .../gallery/screen/dialogs/FlyoutScreen.kt | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/dialogs/FlyoutScreen.kt diff --git a/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/dialogs/FlyoutScreen.kt b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/dialogs/FlyoutScreen.kt new file mode 100644 index 00000000..492c0ff2 --- /dev/null +++ b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/dialogs/FlyoutScreen.kt @@ -0,0 +1,65 @@ +package com.konyaco.fluent.gallery.screen.dialogs + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.konyaco.fluent.FluentTheme +import com.konyaco.fluent.component.Button +import com.konyaco.fluent.component.FlyoutContainer +import com.konyaco.fluent.component.Text +import com.konyaco.fluent.gallery.annotation.Component +import com.konyaco.fluent.gallery.annotation.Sample +import com.konyaco.fluent.gallery.component.ComponentPagePath +import com.konyaco.fluent.gallery.component.GalleryPage +import com.konyaco.fluent.source.generated.FluentSourceFile + +@Component(description = "Shows contextual information and enables user interaction.") +@Composable +fun FlyoutScreen() { + GalleryPage( + title = "Flyout", + description = "A Flyout displays lightweight UI that is either information, " + + "or requires user interaction. Unlike a dialog, a Flyout can be light dismissed by clicking or tapping off of it. " + + "Use it to collect input from the user, show more details about an item, or ask the user to confirm an action.", + componentPath = FluentSourceFile.Flyout, + galleryPath = ComponentPagePath.FlyoutScreen + ) { + Section( + title = "A button with flyout", + sourceCode = sourceCodeOfBasicFlyoutSample, + content = { BasicFlyoutSample() } + ) + } +} + +@Sample +@Composable +private fun BasicFlyoutSample() { + FlyoutContainer( + flyout = { + Column { + Text( + text = "All items will be removed. Do you want to continue?", + style = FluentTheme.typography.bodyStrong, + modifier = Modifier.padding(bottom = 12.dp) + ) + Button( + onClick = { isFlyoutVisible = false }, + content = { + Text("Yes, empty my cart") + } + ) + } + }, + content = { + Button( + onClick = { isFlyoutVisible = !isFlyoutVisible }, + content = { + Text("Empty cart") + } + ) + } + ) +} \ No newline at end of file From 481daf810bcebf72dcf187cdd0c9feb8f1618a53 Mon Sep 17 00:00:00 2001 From: sanlorng Date: Thu, 5 Dec 2024 12:37:55 +0800 Subject: [PATCH 3/4] [gallery] feat: Add MenuFlyout component samples This commit introduces the `MenuFlyout` component, a lightweight UI element that displays a list of commands or options. - It can be triggered by a `Button`, `SubtleButton`, or other interactive elements. - It supports selectable items, separators, cascading menus, and icons. - It can be customized with keyboard accelerators. The `MenuFlyout` is designed to be light dismissed by clicking or tapping outside of it. It provides a contextual menu for users to interact with an application. --- .../screen/menus/MenuFlyoutScreen.android.kt | 3 + .../gallery/screen/menus/MenuFlyoutScreen.kt | 335 ++++++++++++++++++ .../screen/menus/MenuFlyoutScreen.skiko.kt | 7 + 3 files changed, 345 insertions(+) create mode 100644 gallery/src/androidMain/kotlin/com/konyaco/fluent/gallery/screen/menus/MenuFlyoutScreen.android.kt create mode 100644 gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/menus/MenuFlyoutScreen.kt create mode 100644 gallery/src/skikoMain/kotlin/com/konyaco/fluent/gallery/screen/menus/MenuFlyoutScreen.skiko.kt diff --git a/gallery/src/androidMain/kotlin/com/konyaco/fluent/gallery/screen/menus/MenuFlyoutScreen.android.kt b/gallery/src/androidMain/kotlin/com/konyaco/fluent/gallery/screen/menus/MenuFlyoutScreen.android.kt new file mode 100644 index 00000000..83d743ce --- /dev/null +++ b/gallery/src/androidMain/kotlin/com/konyaco/fluent/gallery/screen/menus/MenuFlyoutScreen.android.kt @@ -0,0 +1,3 @@ +package com.konyaco.fluent.gallery.screen.menus + +actual fun isMacOs(): Boolean = false \ No newline at end of file diff --git a/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/menus/MenuFlyoutScreen.kt b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/menus/MenuFlyoutScreen.kt new file mode 100644 index 00000000..ad21907a --- /dev/null +++ b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/menus/MenuFlyoutScreen.kt @@ -0,0 +1,335 @@ +package com.konyaco.fluent.gallery.screen.menus + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.isCtrlPressed +import androidx.compose.ui.input.key.isMetaPressed +import androidx.compose.ui.input.key.key +import androidx.compose.ui.unit.dp +import com.konyaco.fluent.component.Button +import com.konyaco.fluent.component.Icon +import com.konyaco.fluent.component.ListItemDefaults +import com.konyaco.fluent.component.ListItemSelectionType +import com.konyaco.fluent.component.MenuFlyoutContainer +import com.konyaco.fluent.component.MenuFlyoutItem +import com.konyaco.fluent.component.MenuFlyoutSeparator +import com.konyaco.fluent.component.SubtleButton +import com.konyaco.fluent.component.Text +import com.konyaco.fluent.component.commandBarButtonSize +import com.konyaco.fluent.gallery.annotation.Component +import com.konyaco.fluent.gallery.annotation.Sample +import com.konyaco.fluent.gallery.component.ComponentPagePath +import com.konyaco.fluent.gallery.component.GalleryPage +import com.konyaco.fluent.icons.Icons +import com.konyaco.fluent.icons.regular.ArrowSort +import com.konyaco.fluent.icons.regular.ChevronRight +import com.konyaco.fluent.icons.regular.Copy +import com.konyaco.fluent.icons.regular.Delete +import com.konyaco.fluent.icons.regular.Share +import com.konyaco.fluent.source.generated.FluentSourceFile + +@Component(description = "Shows a contextual list of simple commands or options.") +@Composable +fun MenuFlyoutScreen() { + GalleryPage( + title = "MenuFlyout", + description = "A MenuFlyout displays lightweight UI that is light dismissed by clicking or tapping off of it. " + + "Use it to let the user choose from a contextual list of simple commands or options.", + componentPath = FluentSourceFile.MenuFlyout, + galleryPath = ComponentPagePath.MenuFlyoutScreen + ) { + Section( + title = "A CommandBarButton with MenuFlyout", + sourceCode = sourceCodeOfBasicMenuFlyoutSample, + content = { BasicMenuFlyoutSample() } + ) + + Section( + title = "A MenuFlyout with SelectableMenuFlyoutItems and MenuFlyoutSeparator", + sourceCode = sourceCodeOfSelectableMenuFlyoutSample, + content = { SelectableMenuFlyoutSample() } + ) + + Section( + title = "A MenuFlyout with Cascading menus.", + sourceCode = sourceCodeOfCascadingMenuFlyoutSample, + content = { CascadingMenuFlyoutSample() } + ) + + Section( + title = "A MenuFlyout with Icons.", + sourceCode = sourceCodeOfMenuFlyoutWithIconSample, + content = { MenuFlyoutWithIconSample() } + ) + + Section( + title = "A MenuFlyout with Keyboard Accelerators.", + sourceCode = sourceCodeOfMenuFlyoutWithKeyboardSample, + content = { MenuFlyoutWithKeyboardSample() } + ) + } +} + +@Sample +@Composable +private fun BasicMenuFlyoutSample() { + MenuFlyoutContainer( + flyout = { + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + text = { Text("By rating") } + ) + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + text = { Text("By match") } + ) + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + text = { Text("By distance") } + ) + }, + content = { + SubtleButton( + onClick = { isFlyoutVisible = !isFlyoutVisible }, + content = { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + Icon(Icons.Default.ArrowSort, contentDescription = null) + Icon(Icons.Default.ChevronRight, contentDescription = null, modifier = Modifier.size(12.dp)) + } + }, + modifier = Modifier.commandBarButtonSize() + ) + } + + ) +} + +@Sample +@Composable +private fun SelectableMenuFlyoutSample() { + var resetKey by remember { mutableStateOf(0) } + val options = remember(resetKey) { + listOf( + "Repeat" to mutableStateOf(true), + "Shuffle" to mutableStateOf(true), + ) + } + MenuFlyoutContainer( + flyout = { + MenuFlyoutItem( + onClick = { + resetKey += 1 + isFlyoutVisible = false + }, + icon = { }, + text = { Text("Reset") } + ) + MenuFlyoutSeparator() + options.forEach { (name, state) -> + MenuFlyoutItem( + selected = state.value, + onSelectedChanged = { + state.value = it + isFlyoutVisible = false + }, + selectionType = ListItemSelectionType.Radio, + colors = ListItemDefaults.defaultListItemColors(), + text = { Text(name) } + ) + } + }, + content = { + Button( + onClick = { isFlyoutVisible = !isFlyoutVisible }, + content = { Text("Options") } + ) + } + ) +} + +@Sample +@Composable +private fun CascadingMenuFlyoutSample() { + MenuFlyoutContainer( + flyout = { + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + text = { Text("Open") }, + ) + MenuFlyoutItem( + text = { Text("Send to") }, + items = { + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + text = { Text("Bluetooth") } + ) + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + text = { Text("Desktop (shortcut)") } + ) + MenuFlyoutItem( + text = { Text("Compressed file") }, + items = { + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + text = { Text("Compress and email") } + ) + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + text = { Text("Compress to .7z") } + ) + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + text = { Text("Compress to .zip") } + ) + + } + ) + } + ) + }, + content = { + Button( + onClick = { isFlyoutVisible = !isFlyoutVisible }, + content = { Text("File Options") } + ) + } + ) +} + +@Sample +@Composable +private fun MenuFlyoutWithIconSample() { + MenuFlyoutContainer( + flyout = { + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + icon = { Icon(Icons.Default.Share, contentDescription = null) }, + text = { Text("Share") } + ) + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + icon = { Icon(Icons.Default.Copy, contentDescription = null) }, + text = { Text("Copy") } + ) + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + icon = { Icon(Icons.Default.Delete, contentDescription = null) }, + text = { Text("Delete") } + ) + MenuFlyoutSeparator() + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + icon = { }, + text = { Text("Rename") } + ) + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + icon = { }, + text = { Text("Select") } + ) + }, + content = { + Button( + onClick = { isFlyoutVisible = !isFlyoutVisible }, + content = { Text("Edit Options") } + ) + } + ) +} + +@Sample +@Composable +private fun MenuFlyoutWithKeyboardSample() { + val isMacOs = remember { isMacOs() } + var closeAction by remember { mutableStateOf({}) } + MenuFlyoutContainer( + onKeyEvent = { + val key = it.key + when(key) { + Key.C, Key.S, Key.Delete -> { + val accept = when { + Key.Delete == key -> true + isMacOs -> it.isMetaPressed + else -> it.isCtrlPressed + } + if (accept) { + closeAction() + true + } else { + false + } + } + + else -> false + } + }, + flyout = { + LaunchedEffect(this, closeAction) { + closeAction = { isFlyoutVisible = false } + } + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + icon = { Icon(Icons.Default.Share, contentDescription = null) }, + text = { Text("Share") }, + trailing = { + if (isMacOs) { + Text("⌘ S") + } else { + Text("Ctrl+S") + } + } + ) + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + icon = { Icon(Icons.Default.Copy, contentDescription = null) }, + text = { Text("Copy") }, + trailing = { + if (isMacOs) { + Text("⌘ C") + } else { + Text("Ctrl+C") + } + } + ) + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + icon = { Icon(Icons.Default.Delete, contentDescription = null) }, + text = { Text("Delete") }, + trailing = { Text("Delete") } + ) + MenuFlyoutSeparator() + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + icon = { }, + text = { Text("Rename") } + ) + MenuFlyoutItem( + onClick = { isFlyoutVisible = false }, + icon = { }, + text = { Text("Select") } + ) + }, + content = { + Button( + onClick = { isFlyoutVisible = !isFlyoutVisible }, + content = { Text("Edit Options") } + ) + } + ) +} + +expect fun isMacOs(): Boolean \ No newline at end of file diff --git a/gallery/src/skikoMain/kotlin/com/konyaco/fluent/gallery/screen/menus/MenuFlyoutScreen.skiko.kt b/gallery/src/skikoMain/kotlin/com/konyaco/fluent/gallery/screen/menus/MenuFlyoutScreen.skiko.kt new file mode 100644 index 00000000..ab34604e --- /dev/null +++ b/gallery/src/skikoMain/kotlin/com/konyaco/fluent/gallery/screen/menus/MenuFlyoutScreen.skiko.kt @@ -0,0 +1,7 @@ +package com.konyaco.fluent.gallery.screen.menus + +import org.jetbrains.skiko.hostOs + +actual fun isMacOs(): Boolean { + return hostOs.isMacOS +} \ No newline at end of file From 56c6fcefae431a4cd8d357d691bf96c934ee55ed Mon Sep 17 00:00:00 2001 From: sanlorng Date: Sun, 8 Dec 2024 09:50:14 +0800 Subject: [PATCH 4/4] [gallery] feat: Add source code view for ComboBox This commit adds the ability to view the source code for the ComboBox component in the Fluent Gallery app. It links the ComboBox component in the gallery to its source code file, allowing users to easily access and review the implementation details. --- .../fluent/gallery/screen/basicinput/ComboBoxScreen.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/basicinput/ComboBoxScreen.kt b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/basicinput/ComboBoxScreen.kt index de659c5e..cb87a28c 100644 --- a/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/basicinput/ComboBoxScreen.kt +++ b/gallery/src/commonMain/kotlin/com/konyaco/fluent/gallery/screen/basicinput/ComboBoxScreen.kt @@ -11,6 +11,7 @@ import com.konyaco.fluent.gallery.annotation.Sample import com.konyaco.fluent.gallery.component.ComponentPagePath import com.konyaco.fluent.gallery.component.GalleryPage import com.konyaco.fluent.gallery.component.TodoComponent +import com.konyaco.fluent.source.generated.FluentSourceFile @Component(index = 9, description = "A drop-down list of items a user can select from.") @Composable @@ -18,7 +19,8 @@ fun ComboBoxScreen() { GalleryPage( title = "ComboBox", description = "Use a ComboBox when you need to conserve on-screen space and when users select only one option at a time. A ComboBox shows only the currently selected item.", - galleryPath = ComponentPagePath.ComboBoxScreen + galleryPath = ComponentPagePath.ComboBoxScreen, + componentPath = FluentSourceFile.ComboBox ) { Section( title = "A ComboBox with its Items source set",