From 1a8bd1d52c048ee5698261c0584c63e66cf14f23 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Wed, 24 Aug 2022 13:35:45 -0300 Subject: [PATCH 1/7] Update inputs/ documentation --- README.md | 282 ------------------ example/lib/screens/inputs/button.dart | 96 +++--- example/lib/screens/inputs/checkbox.dart | 9 +- example/lib/screens/inputs/slider.dart | 20 +- example/lib/screens/inputs/toggle_switch.dart | 18 +- example/lib/widgets/page.dart | 12 + 6 files changed, 96 insertions(+), 341 deletions(-) diff --git a/README.md b/README.md index e62a1bb86..eedfc3999 100644 --- a/README.md +++ b/README.md @@ -653,288 +653,6 @@ ScaffoldPage( Inputs are widgets that reacts to user interection. On most of the inputs you can set `onPressed` or `onChanged` to `null` to disable it. -## Button - -A button gives the user a way to trigger an immediate action. [Learn more](https://docs.microsoft.com/en-us/windows/apps/design/controls/buttons) - -Here's an example of how to create a basic button: - -```dart -Button( - child: Text('Standard XAML button'), - // Set onPressed to null to disable the button - onPressed: () { - print('button pressed'); - } -) -``` - -The code above produces the following: - -![Button](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/button.png) - -You can also use some alternative buttons: - -### Filled Button - -This button is identical to the `Button`, but with accent color fill in background - -```dart -FilledButton( - child: Text('FILLED BUTTON'), - onPressed: () { - print('pressed filled button'); - }, -), -``` - -### Icon Button - -This button is used to display an `Icon` as content. It's optmized to show icons. - -```dart -IconButton( - icon: Icon(FluentIcons.add), - onPressed: () { - print('pressed icon button'); - }, -), -``` - -### Outlined Button - - ```dart - OutlinedButton( - child: Text('OUTLINED BUTTON'), - onPressed: () { - print('pressed outlined button'); - }, - ), - ``` - -### Text Button - -```dart -TextButton( - child: Text('TEXT BUTTON'), - onPressed: () { - print('pressed text button'); - }, -), -``` - -## Split Button - -A Split Button has two parts that can be invoked separately. One part behaves like a standard button and invokes an immediate action. The other part invokes a flyout that contains additional options that the user can choose from. [Learn more](https://docs.microsoft.com/en-us/windows/apps/design/controls/buttons#create-a-split-button) - -You can use a `SplitButtonBar` to create a Split Button. It takes two `Button`s in the `buttons` property. You can also customize the button spacing by changing the property `interval` in its theme. - -Here's an example of how to create a split button: - -```dart -const double splitButtonHeight = 25.0; - -SplitButtonBar( - style: SplitButtonThemeData( - interval: 1, // the default value is one - ), - // There need to be at least 2 items in the buttons, and they must be non-null - buttons: [ - SizedBox( - height: splitButtonHeight, - child: Button( - child: Container( - height: 24, - width: 24, - color: FluentTheme.of(context).accentColor, - ), - onPressed: () {}, - ), - ), - IconButton( - icon: const SizedBox( - height: splitButtonHeight, - child: const Icon(FluentIcons.chevron_down, size: 10.0), - ), - onPressed: () {}, - ), - ], -) -``` - -The code above produces the following button: - -![SplitButtonBar Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/split-button-rtb.png) - -## Toggle Button - -A button that can be on or off. - -Here's an example of how to create a basic toggle button: - -```dart -bool _value = false; - -ToggleButton( - child: Text('Toggle Button'), - checked: _value, - onChanged: (value) => setState(() => _value = value), -) -``` - -## Checkbox - -A check box is used to select or deselect action items. It can be used for a single item or for a list of multiple items that a user can choose from. The control has three selection states: unselected, selected, and indeterminate. Use the indeterminate state when a collection of sub-choices have both unselected and selected states. [Learn more](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/checkbox) - -Here's an example of how to create a checkbox: - -```dart -bool _checked = true; - -Checkbox( - checked: _checked, - onChanged: (value) => setState(() => _checked = value), -) -``` - -### Handling its states - -| State | Property | Value | -| ------------- | --------- | -------- | -| checked | checked | `true` | -| unchecked | checked | `false` | -| indeterminate | checked | `null` | -| enabled | onChanged | non-null | -| disabled | onChanged | `null` | - -![Checkbox states](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/templates-checkbox-states-default.png) - -## Toggle Switch - -The toggle switch represents a physical switch that allows users to turn things on or off, like a light switch. Use toggle switch controls to present users with two mutually exclusive options (such as on/off), where choosing an option provides immediate results. [Learn more](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/toggles) - -Here's an example of how to create a basic toggle switch: - -```dart -bool _checked = false; - -ToggleSwitch( - checked: _checked, - onChanged: (v) => setState(() => _checked = v), - content: Text(_checked ? 'On' : 'Off'); -) -``` - -![Toggle Switch states](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/toggleswitches01.png) - -## Radio Buttons - -Radio buttons, also called option buttons, let users select one option from a collection of two or more mutually exclusive, but related, options. Radio buttons are always used in groups, and each option is represented by one radio button in the group. - -In the default state, no radio button in a RadioButtons group is selected. That is, all radio buttons are cleared. However, once a user has selected a radio button, the user can't deselect the button to restore the group to its initial cleared state. - -The singular behavior of a RadioButtons group distinguishes it from check boxes, which support multi-selection and deselection, or clearing. - -[Learn more](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/radio-button) - -Here's an example of how to create a basic set of radio buttons: - -```dart -int _currentIndex = -1; - -final List radioButtons = [ - 'RadioButton 1', - 'RadioButton 2', - 'RadioButton 3', -]; - -Column( - children: List.generate(radioButtons.length, (index) { - return RadioButton( - checked: _currentIndex == index, - // set onChanged to null to disable the button - onChanged: (value) => setState(() => _currentIndex = index), - content: Text(radioButtons[index]), - ); - }), -), -``` - -The code above produces the following: - -![Radio Buttons](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/controls/radio-button.png) - -## DropDown button - -A DropDownButton is a button that shows a chevron as a visual indicator that it has an attached flyout that contains more options. [Learn more](https://docs.microsoft.com/en-us/windows/apps/design/controls/buttons#create-a-drop-down-button) - -![DropDown Button](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/drop-down-button-align.png) - -Here's an example of how to create a drop down button: - -```dart -DropDownButton( - leading: const Icon(FluentIcons.align_left), - title: const Text('Alignment'), - items: [ - MenuFlyoutItem( - text: const Text('Left'), - leading: const Icon(FluentIcons.align_left), - onPressed: () => debugPrint('left'), - ), - MenuFlyoutItem( - text: const Text('Center'), - leading: const Icon(FluentIcons.align_center), - onPressed: () => debugPrint('center'), - ), - MenuFlyoutItem( - text: const Text('Right'), - leading: const Icon(FluentIcons.align_right), - onPressed: () => debugPrint('right'), - ), - ], -); -``` - -## Slider - -A slider is a control that lets the user select from a range of values by moving a thumb control along a track. [Learn more](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/slider) - -A slider is a good choice when you know that users think of the value as a relative quantity, not a numeric value. For example, users think about setting their audio volume to low or medium—not about setting the value to 2 or 5. - -Don't use a slider for binary settings. Use a [toggle switch](#toggle-switches) instead. - -Here's an example of how to create a basic slider: - -```dart -double _sliderValue = 0; - -SizedBox( - // The default width is 200. - // The slider does not have its own widget, so you have to add it yourself. - // The slider always try to be as big as possible - width: 200, - child: Slider( - max: 100, - value: _sliderValue, - onChanged: (v) => setState(() => _sliderValue = v), - // Label is the text displayed above the slider when the user is interacting with it. - label: '${_sliderValue.toInt()}', - ), -) -``` - -The code above produces the following: - -![Slider Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/controls/slider.png) - -### Choosing between vertical and horizontal sliders - -You can set `vertical` to `true` to create a vertical slider - -| Horizontal | Vertical | -| ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -| If the control is used to seek within media, like in a video app. | if the slider represents a real-world value that is normally shown vertically (such as temperature). | ## Rating Bar diff --git a/example/lib/screens/inputs/button.dart b/example/lib/screens/inputs/button.dart index f78235350..923777c9a 100644 --- a/example/lib/screens/inputs/button.dart +++ b/example/lib/screens/inputs/button.dart @@ -27,6 +27,9 @@ class ButtonPage extends ScrollablePage { 'The Button control provides a Click event to respond to user input from a touch, mouse, keyboard, stylus, or other input device. You can put different kinds of content in a button, such as text or an image, or you can restyle a button to give it a new look.', ), subtitle(content: const Text('A simple button with text content')), + description( + content: const Text('A button that initiates an immediate action.'), + ), CardHighlight( child: Row(children: [ Button( @@ -98,8 +101,11 @@ class ButtonPage extends ScrollablePage { )''', ), subtitle(content: const Text('A simple ToggleButton with text content')), - const Text( - 'A ToggleButton looks like a Button, but works like a CheckBox. It typically has two states, checked (on) or unchecked (off).', + description( + content: const Text( + 'A ToggleButton looks like a Button, but works like a CheckBox. It ' + 'typically has two states, checked (on) or unchecked (off).', + ), ), CardHighlight( child: Row(children: [ @@ -179,47 +185,52 @@ ToggleButton( )''', ), subtitle(content: const Text('SplitButton')), + description( + content: const Text( + 'Represents a button with two parts that can be invoked separately. ' + 'One part behaves like a standard button and the other part invokes ' + 'a flyout.', + ), + ), CardHighlight( - child: Row( - children: [ - SplitButtonBar( - buttons: [ - Button( - child: Container( - decoration: BoxDecoration( - color: splitButtonDisabled - ? FluentTheme.of(context).accentColor.darker - : FluentTheme.of(context).accentColor, - borderRadius: const BorderRadiusDirectional.horizontal( - start: Radius.circular(4.0), - ), + child: Row(children: [ + SplitButtonBar( + buttons: [ + Button( + child: Container( + decoration: BoxDecoration( + color: splitButtonDisabled + ? FluentTheme.of(context).accentColor.darker + : FluentTheme.of(context).accentColor, + borderRadius: const BorderRadiusDirectional.horizontal( + start: Radius.circular(4.0), ), - height: 24, - width: 24, ), - onPressed: splitButtonDisabled ? null : () {}, + height: 24, + width: 24, ), - IconButton( - icon: const SizedBox( - // height: splitButtonHeight, - child: Icon(FluentIcons.chevron_down, size: 10.0), - ), - onPressed: splitButtonDisabled ? null : () {}, + onPressed: splitButtonDisabled ? null : () {}, + ), + IconButton( + icon: const SizedBox( + // height: splitButtonHeight, + child: Icon(FluentIcons.chevron_down, size: 10.0), ), - ], - ), - const Spacer(), - ToggleSwitch( - checked: splitButtonDisabled, - onChanged: (v) { - setState(() { - splitButtonDisabled = v; - }); - }, - content: const Text('Disabled'), - ), - ], - ), + onPressed: splitButtonDisabled ? null : () {}, + ), + ], + ), + const Spacer(), + ToggleSwitch( + checked: splitButtonDisabled, + onChanged: (v) { + setState(() { + splitButtonDisabled = v; + }); + }, + content: const Text('Disabled'), + ), + ]), codeSnippet: '''SplitButtonBar( buttons: [ Button( @@ -238,8 +249,13 @@ ToggleButton( )''', ), subtitle(content: const Text('RadioButton')), - const Text( - 'A control that allows a user to select a single option from a group of options', + description( + content: const Text( + 'Radio buttons, also called option buttons, let users select one option ' + 'from a collection of two or more mutually exclusive, but related, ' + 'options. Radio buttons are always used in groups, and each option is ' + 'represented by one radio button in the group.', + ), ), CardHighlight( child: Row(children: [ diff --git a/example/lib/screens/inputs/checkbox.dart b/example/lib/screens/inputs/checkbox.dart index 7621752b0..876312f1f 100644 --- a/example/lib/screens/inputs/checkbox.dart +++ b/example/lib/screens/inputs/checkbox.dart @@ -25,13 +25,8 @@ class CheckboxPage extends ScrollablePage { child: Row(children: [ Checkbox( checked: firstChecked, - onChanged: firstDisabled - ? null - : (v) { - setState(() { - firstChecked = v!; - }); - }, + onChanged: + firstDisabled ? null : (v) => setState(() => firstChecked = v!), content: const Text('Two-state Checkbox'), ), const Spacer(), diff --git a/example/lib/screens/inputs/slider.dart b/example/lib/screens/inputs/slider.dart index 6956e1467..9fabc9507 100644 --- a/example/lib/screens/inputs/slider.dart +++ b/example/lib/screens/inputs/slider.dart @@ -23,7 +23,14 @@ class SliderPage extends ScrollablePage { List buildScrollable(BuildContext context) { return [ const Text( - 'Use a Slider when you want your users to be able to set defined, contiguous values (such as volume or brightness) or a range of discrete values (such as screen resolution settings).'), + 'Use a Slider when you want your users to be able to set defined, ' + 'contiguous values (such as volume or brightness) or a range of discrete ' + 'values (such as screen resolution settings).\n\n' + 'A slider is a good choice when you know that users think of the value ' + 'as a relative quantity, not a numeric value. For example, users think ' + 'about setting their audio volume to low or medium—not about setting ' + 'the value to 2 or 5.', + ), subtitle(content: const Text('A simple Slider')), CardHighlight( child: Row(children: [ @@ -49,6 +56,16 @@ Slider( ''', ), subtitle(content: const Text('A vertical slider')), + description( + content: const Text( + '''You can orient your slider horizontally or vertically. Use these guidelines to determine which layout to use. + + * Use a natural orientation. For example, if the slider represents a real-world value that is normally shown vertically (such as temperature), use a vertical orientation. + * If the control is used to seek within media, like in a video app, use a horizontal orientation. + * When using a slider in page that can be panned in one direction (horizontally or vertically), use a different orientation for the slider than the panning direction. Otherwise, users might swipe the slider and change its value accidentally when they try to pan the page. + * If you're still not sure which orientation to use, use the one that best fits your page layout.''', + ), + ), CardHighlight( child: Row(children: [ Slider( @@ -71,6 +88,7 @@ Slider( ), ''', ), + // TODO: slider label ]; } } diff --git a/example/lib/screens/inputs/toggle_switch.dart b/example/lib/screens/inputs/toggle_switch.dart index 9a87dd218..c628330c5 100644 --- a/example/lib/screens/inputs/toggle_switch.dart +++ b/example/lib/screens/inputs/toggle_switch.dart @@ -23,7 +23,10 @@ class ToggleSwitchPage extends ScrollablePage { List buildScrollable(BuildContext context) { return [ const Text( - 'Use ToggleSwitch controls to present users with exactly two mutually exclusive options (like on/off), where choosing an option results in an immediate commit. A toggle switch should have a single label', + 'The toggle switch represents a physical switch that allows users to ' + 'turn things on or off, like a light switch. Use toggle switch controls ' + 'to present users with two mutually exclusive options (such as on/off), ' + 'where choosing an option provides immediate results.', ), subtitle(content: const Text('A simple ToggleSwitch')), CardHighlight( @@ -31,11 +34,7 @@ class ToggleSwitchPage extends ScrollablePage { alignment: Alignment.centerLeft, child: ToggleSwitch( checked: firstValue, - onChanged: disabled - ? null - : (v) { - setState(() => firstValue = v); - }, + onChanged: disabled ? null : (v) => setState(() => firstValue = v), content: Text(firstValue ? 'On' : 'Off'), ), ), @@ -55,11 +54,8 @@ ToggleSwitch( label: 'Header', child: ToggleSwitch( checked: secondValue, - onChanged: disabled - ? null - : (v) { - setState(() => secondValue = v); - }, + onChanged: + disabled ? null : (v) => setState(() => secondValue = v), content: Text(secondValue ? 'Working' : 'Do work'), ), ), diff --git a/example/lib/widgets/page.dart b/example/lib/widgets/page.dart index 4bc4ff1af..846d41e2f 100644 --- a/example/lib/widgets/page.dart +++ b/example/lib/widgets/page.dart @@ -41,6 +41,18 @@ abstract class ScrollablePage extends Page { ); } + Widget description({required Widget content}) { + return Builder(builder: (context) { + return Padding( + padding: const EdgeInsets.only(bottom: 4.0), + child: DefaultTextStyle( + style: FluentTheme.of(context).typography.body!, + child: content, + ), + ); + }); + } + Widget subtitle({required Widget content}) { return Builder(builder: (context) { return Padding( From 869d1d98e3f6bf8866eb21fcf4138cbcb189e42f Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Wed, 24 Aug 2022 13:54:54 -0300 Subject: [PATCH 2/7] Update form/ documentation --- README.md | 196 ------------------ .../lib/screens/forms/auto_suggest_box.dart | 4 +- example/lib/screens/forms/combobox.dart | 21 +- example/lib/screens/forms/date_picker.dart | 9 +- example/lib/screens/forms/text_box.dart | 10 +- example/lib/screens/forms/time_picker.dart | 9 +- 6 files changed, 46 insertions(+), 203 deletions(-) diff --git a/README.md b/README.md index eedfc3999..0eded1d54 100644 --- a/README.md +++ b/README.md @@ -675,179 +675,6 @@ You can set `amount` to change the amount of stars. The `rating` must be less th ![Rating Bar](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/rating_rs2_doc_ratings_intro.png) -# Forms - -A form is a group of controls that collect and submit data from users. Forms are typically used for settings pages, surveys, creating accounts, and much more. - -## TextBox - -A Text Box lets a user type text into an app. It's typically used to capture a single line of text, but can be configured to capture multiple lines of text. The text displays on the screen in a simple, uniform, plaintext format. [Learn more](https://docs.microsoft.com/en-us/windows/apps/design/controls/text-box) - -![TextBox Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/text-box.png) - -You can use the [Forms screen](example/lib/screens/forms.dart) in the example app for reference. - -You can use the widget `TextBox` to create text boxes: - -```dart -TextBox( - controller: ..., - header: 'Notes', - placeholder: 'Type your notes here', -), -``` - -Which produces the following: - -![TextBox Example Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/text-box-ex1.png) - -If you want to validate the text box, use a `TextFormBox`: - -```dart -TextFormBox( - placeholder: 'Your email', - validator: (text) { - if (text == null || text.isEmpty) return 'Provide an email'; - } -), -``` - -## Auto Suggest Box - -Use an AutoSuggestBox to provide a list of suggestions for a user to select from as they type. [Learn more](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/auto-suggest-box) - -![AutoSuggestBox example](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/controls-autosuggest-expanded-01.png) - -Here's an example of how to create an AutoSuggestBox - -```dart -AutoSuggestBox( - placeholder: 'Select a cat breed', - items: [ - 'Chatilly-Tifanny', - 'Chartreux', - 'Chausie', - 'Munchkin', - 'York Chocolate', - ].map((breed) { - return AutoSuggestBoxItem( - value: breed, // Takes a String - onFocusChange: (focused) { - if (focused) print('Focused $breed'); - }, - onSelected: () { - print('Selected $breed'); - }, - ) - }).toList(), - onSelected: (breed) { - print(breed); - }, -) -``` - -## ComboBox - -Use a combo box (also known as a drop-down list) to present a list of items that a user can select from. A combo box starts in a compact state and expands to show a list of selectable items. [Learn more](https://docs.microsoft.com/en-us/windows/apps/design/controls/combo-box) - -Here's an example of how to create a basic combo box: - -```dart - -final values = ['Blue', 'Green', 'Yellow', 'Red']; -String? comboBoxValue; - -SizedBox( - width: 200, - child: ComboBox( - placeholder: Text('Selected list item'), - isExpanded: true, - items: values - .map((e) => ComboboxItem( - value: e, - child: Text(e), - )) - .toList(), - value: comboBoxValue, - onChanged: (value) { - if (value != null) setState(() => comboBoxValue = value); - }, - ), -), -``` - -The code above produces the following: - -![Combo box Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/combo-box-no-selection.png) - -## EditableComboBox - -By default, a combo box lets the user select from a pre-defined list of options. However, there are cases where the list contains only a subset of valid values, and the user should be able to enter other values that aren't listed. To support this, you can make the combo box editable. [Learn more](https://docs.microsoft.com/en-us/windows/apps/design/controls/combo-box#make-a-combo-box-editable) - -Here's an example of how to create an editable combo box: - -```dart -static const fontSizes = [ - 8, - 9, - ..., -]; - -double fontSize = 20.0; - -EditableComboBox( - value: fontSize.toInt(), - items: cats.map>((e) { - return ComboboxItem( - child: Text('\$e'), - value: e.toInt(), - ); - }).toList(), - onChanged: disabled - ? null - : (size) { - setState(() => fontSize = size?.toDouble() ?? fontSize); - }, - placeholder: const Text('Select a font size'), - onFieldSubmitted: (String text) { - // When the value in the text field is changed, this callback is called - // It's up to the developer to handle the text change - - try { - final newSize = int.parse(text); - - if (newSize < 8 || newSize > 100) { - throw UnsupportedError( - 'The font size must be a number between 8 and 100.', - ); - } - - setState(() => fontSize = newSize.toDouble()); - } catch (e) { - showDialog( - context: context, - builder: (context) { - return ContentDialog( - content: const Text( - 'The font size must be a number between 8 and 100.', - ), - actions: [ - FilledButton( - child: const Text('Close'), - onPressed: Navigator.of(context).pop, - ), - ], - ); - }, - ); - } - return fontSize.toInt().toString(); - }, -), -``` - -`onFieldSubmitted` is called when the text of the undelaying `TextBox` is submitted. It has the `text` paramter, which is the value of the text box. It must return a `String`, which is going to be the new text of the text box. - # Widgets ## Tooltip @@ -1032,29 +859,6 @@ Which produces the following: ![InfoBar Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/infobar-default-title-message.png) -## Date Picker - -The date picker gives you a standardized way to let users pick a localized date value using touch, mouse, or keyboard input. [Learn more](https://docs.microsoft.com/en-us/windows/apps/design/controls/date-picker) - -The entry point displays the chosen date, and when the user selects the entry point, a picker surface expands vertically from the middle for the user to make a selection. The date picker overlays other UI; it doesn't push other UI out of the way. - -We use [intl](https://pub.dev/packages/intl) to format the dates. You can [change the current locale](https://pub.dev/packages/intl#current-locale) to change formatting - -Here's an example of how to create a basic date picker: - -```dart -DateTime date = DateTime.now(); - -DatePicker( - header: 'Pick a date', - selected: date, - onChanged: (v) => setState(() => date = v), -); -``` - -Which produces the following: - -![DatePicker Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/controls-datepicker-expand.gif) ## Time Picker diff --git a/example/lib/screens/forms/auto_suggest_box.dart b/example/lib/screens/forms/auto_suggest_box.dart index f677e1300..b84de7b27 100644 --- a/example/lib/screens/forms/auto_suggest_box.dart +++ b/example/lib/screens/forms/auto_suggest_box.dart @@ -14,7 +14,9 @@ class AutoSuggestBoxPage extends ScrollablePage { List buildScrollable(BuildContext context) { return [ const Text( - 'A text control that makes suggestions to users as they type. The app is notified when text has been changed by the user and is responsible for providing relevant suggestions for this control to display.', + 'A text control that makes suggestions to users as they type. The app ' + 'is notified when text has been changed by the user and is responsible ' + 'for providing relevant suggestions for this control to display.', ), subtitle(content: const Text('A basic AutoSuggestBox')), CardHighlight( diff --git a/example/lib/screens/forms/combobox.dart b/example/lib/screens/forms/combobox.dart index ebaee1d6b..4dfd0d717 100644 --- a/example/lib/screens/forms/combobox.dart +++ b/example/lib/screens/forms/combobox.dart @@ -58,7 +58,15 @@ class ComboboxPage extends ScrollablePage { List buildScrollable(BuildContext context) { return [ const Text( - '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.', + 'Use a combo box (also known as a drop-down list) to present a list of ' + 'items that a user can select from. A combo box starts in a compact ' + 'state and expands to show a list of selectable items.\n\n' + 'When the combo box is closed, it either displays the current selection ' + 'or is empty if there is no selected item. When the user expands the ' + 'combo box, it displays the list of selectable items.\n\n' + '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.', ), subtitle( content: const Text( @@ -149,8 +157,15 @@ ComboBox( placeholder: const Text('Select a cat breed'), ),''', ), - subtitle( - content: const Text('An editable ComboBox'), + subtitle(content: const Text('An editable ComboBox')), + description( + content: const Text( + 'By default, a combo box lets the user select from a pre-defined ' + 'list of options. However, there are cases where the list contains ' + 'only a subset of valid values, and the user should be able to enter ' + 'other values that aren\'t listed. To support this, you can make the' + ' combo box editable.', + ), ), CardHighlight( child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/example/lib/screens/forms/date_picker.dart b/example/lib/screens/forms/date_picker.dart index 29f277d99..c2b68d75e 100644 --- a/example/lib/screens/forms/date_picker.dart +++ b/example/lib/screens/forms/date_picker.dart @@ -15,7 +15,14 @@ class DatePickerPage extends ScrollablePage { List buildScrollable(BuildContext context) { return [ const Text( - 'Use a DatePicker to let users set a date in your app, for example to schedule an appointment. The DatePicker displays three controls for month, date, and year. These controls are easy to use with touch or mouse, and they can be styled and configured in several different ways.', + 'Use a DatePicker to let users set a date in your app, for example to ' + 'schedule an appointment. The DatePicker displays three controls for ' + 'month, date, and year. These controls are easy to use with touch or ' + 'mouse, and they can be styled and configured in several different ways.' + '\n\nThe entry point displays the chosen date, and when the user ' + 'selects the entry point, a picker surface expands vertically from the ' + 'middle for the user to make a selection. The date picker overlays ' + 'other UI; it doesn\'t push other UI out of the way.', ), subtitle(content: const Text('A simple DatePicker with a header')), CardHighlight( diff --git a/example/lib/screens/forms/text_box.dart b/example/lib/screens/forms/text_box.dart index e1130aeff..50423cdba 100644 --- a/example/lib/screens/forms/text_box.dart +++ b/example/lib/screens/forms/text_box.dart @@ -12,7 +12,15 @@ class TextBoxPage extends ScrollablePage { List buildScrollable(BuildContext context) { return [ const Text( - 'Use a TextBox to let a user enter simple text input in your app. You can add a header and placeholder text to let the user know what the TextBox is for, and you can customize it in other ways.', + 'The TextBox control lets a user type text into an app. It\'s typically ' + 'used to capture a single line of text, but can be configured to capture ' + 'multiple lines of text. The text displays on the screen in a simple, ' + 'uniform, plaintext format.\n\n' + 'TextBox has a number of features that can simplify text entry. It comes ' + 'with a familiar, built-in context menu with support for copying and ' + 'pasting text. The "clear all" button lets a user quickly delete all ' + 'text that has been entered. It also has spell checking capabilities ' + 'built in and enabled by default.', ), subtitle(content: const Text('A simple TextBox')), CardHighlight( diff --git a/example/lib/screens/forms/time_picker.dart b/example/lib/screens/forms/time_picker.dart index 6f3168a3a..06526652e 100644 --- a/example/lib/screens/forms/time_picker.dart +++ b/example/lib/screens/forms/time_picker.dart @@ -16,7 +16,14 @@ class TimePickerPage extends ScrollablePage { List buildScrollable(BuildContext context) { return [ const Text( - 'Use a TimePicker to let users set a time in your app, for example to set a reminder. The TimePicker displays three controls for hour, minute, and AM/PM. These controls are easy to use with touch or mouse, and they can be styled and configured in several different ways.', + 'Use a TimePicker to let users set a time in your app, for example to ' + 'set a reminder. The TimePicker displays three controls for hour, ' + 'minute, and AM/PM. These controls are easy to use with touch or mouse, ' + 'and they can be styled and configured in several different ways.\n\n' + 'The entry point displays the chosen time, and when the user selects ' + 'the entry point, a picker surface expands vertically from the middle ' + 'for the user to make a selection. The time picker overlays other UI; ' + 'it doesn\'t push other UI out of the way.', ), subtitle(content: const Text('A simple TimePicker')), CardHighlight( From 59df08eb8f38a471062bddd2b13a407f689a803b Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Wed, 24 Aug 2022 15:22:10 -0300 Subject: [PATCH 3/7] Update navigation/ documentation --- README.md | 270 ------------------ example/lib/main.dart | 4 +- .../screens/navigation/navigation_view.dart | 198 +++++++++++++ example/lib/screens/navigation/tab_view.dart | 10 +- example/lib/screens/navigation/tree_view.dart | 210 ++++++++++++-- example/lib/widgets/card_highlight.dart | 8 +- .../navigation_view/pane_items.dart | 6 +- .../navigation/navigation_view/view.dart | 15 +- lib/src/controls/utils/info_badge.dart | 17 +- 9 files changed, 422 insertions(+), 316 deletions(-) create mode 100644 example/lib/screens/navigation/navigation_view.dart diff --git a/README.md b/README.md index 0eded1d54..ef173fadc 100644 --- a/README.md +++ b/README.md @@ -60,27 +60,7 @@ - [Navigation Pane](#navigation-pane) - [Navigation Body](#navigation-body) - [Info Badge](#info-badge) - - [Tab View](#tab-view) - [Bottom Navigation](#bottom-navigation) -- [Inputs](#inputs) - - [Button](#button) - - [Filled Button](#filled-button) - - [Icon Button](#icon-button) - - [Outlined Button](#outlined-button) - - [Text Button](#outlined-button) - - [Split Button](#split-button) - - [Toggle Button](#toggle-button) - - [Checkbox](#checkbox) - - [Toggle Switch](#toggle-switch) - - [Radio Buttons](#radio-buttons) - - [DropDown Button](#dropdown-button) - - [Slider](#slider) - - [Choosing between vertical and horizontal sliders](#choosing-between-vertical-and-horizontal-sliders) -- [Forms](#forms) - - [TextBox](#textbox) - - [Auto Suggest Box](#auto-suggest-box) - - [ComboBox](#combobox) - - [EditableComboBox](#editablecombobox) - [Widgets](#widgets) - [Tooltip](#tooltip) - [Content Dialog](#content-dialog) @@ -91,15 +71,10 @@ - [InfoBar](#infobar) - **TODO** [Calendar View](#calendar-view) - **TODO** [Calendar Date Picker]() - - [Date Picker](#date-picker) - - [Time Picker](#time-picker) - [Progress Bar and Progress Ring](#progress-bar-and-progress-ring) - [Scrollbar](#scrollbar) - [ListTile](#listtile) - [Info Label](#info-label) - - [TreeView](#treeview) - - [Scrollable tree view](#scrollable-tree-view) - - [Lazily load nodes](#lazily-load-nodes) - [CommandBar](#commandbar) - [Mobile Widgets](#mobile-widgets) - [Chip](#chip) @@ -434,181 +409,6 @@ It's avaiable with the widget `HorizontalSlidePageTransition`. The default Flutter Navigation is available on the `FluentApp` widget, that means you can simply call `Navigator.push` and `Navigator.pop` to navigate between routes. See [navigate to a new screen and back](https://flutter.dev/docs/cookbook/navigation/navigation-basics) -## Navigation View - -The NavigationView control provides top-level navigation for your app. It adapts to a variety of screen sizes and supports both _top_ and _left_ navigation styles. - -![Navigation Panel](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/nav-view-header.png) - -### App Bar - -The app bar is the top app bar that every desktop nowadays have. - -```dart -NavigationView( - appBar: NavigationAppBar( - title: Text('Nice App Title :)'), - actions: Row(children: [ - /// These actions are usually the minimize, maximize and close window - ]), - /// If automaticallyImplyLeading is true, a 'back button' will be added to - /// app bar. This property can be overritten by [leading] - automaticallyImplyLeading: true, - ), - ... -) -``` - -### Navigation Pane - -The pane is the pane that can be displayed at the left or at the top. - -```dart -NavigationView( - ..., - pane: NavigationPane( - /// The current selected index - selected: index, - /// Called whenever the current index changes - onChanged: (i) => setState(() => index = i), - displayMode: PaneDisplayMode.auto, - ), - ... -) -``` - -You can change the `displayMode` to make it fit the screen. - -| Name | Screenshot | Info | -| ------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Top | ![PaneDisplayMode.top](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/displaymode-top.png) | The pane is positioned above the content. We recommend top navigation when:
- You have 5 or fewer top-level navigation categories that are equally important, and any additional top-level navigation categories that end up in the dropdown overflow menu are considered less important.
- You need to show all navigation options on screen.
- You want more space for your app content.
- Icons cannot clearly describe your app's navigation categories. | -| Open | ![PaneDisplayMode.open](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/displaymode-left.png) | The pane is expanded and positioned to the left of the content. We recommend _open_ navigation when:
- You have 5-10 equally important top-level navigation categories.
- You want navigation categories to be very prominent, with less space for other app content. | -| Compact | ![PaneDisplayMode.compact](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/displaymode-leftcompact.png) | The pane shows only icons until opened and is positioned to the left of the content. | -| Minimal | ![PaneDisplayMode.minimal](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/displaymode-leftminimal.png) | Only the menu button is shown until the pane is opened. When opened, it's positioned to the left of the content. | -| Auto | ![PaneDisplayMode.auto](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/displaymode-auto.png) | By default, `displayMode` is set to `auto`. In Auto mode, the NavigationView adapts between `minimal` when the window is narrow, to `compact`, and then `open` as the window gets wider. | - -You can customize the selected indicator. By default `StickyNavigationIndicator` is used, but you can also use the old windows indicator: - -```dart -pane: NavigationPane( - indicator: const EndNavigationIndicator(), -) -``` - -### Navigation body - -A navigation body is used to implement page transitions into a navigation view. It knows what is the current display mode of the parent `NavigationView`, if any, and define the page transitions accordingly. - -For top mode, the horizontal page transition is used. For the others, drill in page transition is used. - -You can also supply a builder function to create the pages instead of a list of widgets. For this use the `NavigationBody.builder` constructor. - -```dart -int _currentIndex = 0; - -NavigationView( - ..., - content: NavigationBody(index: _currentIndex, children: [...]), -) -``` - -You can use `NavigationBody.builder` - -```dart -NavigationView( - ..., - content: NavigationBody.builder( - index: _currentIndex, - itemBuilder: (context, index) { - return ...; - } - ) -) -``` - -`ScaffoldPage` is usually used with the navigation body as its children: - -```dart -NavigationBody( - index: _currentIndex, - children: [ - const ScaffoldPage( - header: PageHeader(title: Text('Your Songs')) - ) - ], -) -``` - -### Info Badge - -Badging is a non-intrusive and intuitive way to display notifications or bring focus to an area within an app - whether that be for notifications, indicating new content, or showing an alert. An `InfoBadge` is a small piece of UI that can be added into an app and customized to display a number, icon, or a simple dot. [Learn more](https://docs.microsoft.com/en-us/windows/apps/design/controls/info-badge) - -![InfoBadge Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/infobadge/infobadge-example-1.png) - -InfoBadge is built into `NavigationView`, but can also be placed as a standalone widget, allowing you to place InfoBadge into any control or piece of UI of your choosing. When you use an `InfoBadge` somewhere other than `NavigationView`, you are responsible for programmatically determining when to show and dismiss the `InfoBadge`, and where to place the `InfoBadge`. - -Here's an example of how to add an info badge to a `PaneItem`: - -```dart -NavigationView( - ..., - pane: NavigationPane( - ... - children: [ - PaneItem( - icon: Icon(FluentIcons.more), - title: const Text('Others'), - infoBadge: const InfoBadge( - source: Text('9'), - ), - ), - ], - ), - ... -) -``` - -Which produces the folllowing effects in the display modes: - -| Open | Compact | Top | -| ---- | ------- | --- | -| ![Open InfoBadge Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/infobadge/navview-expanded.png) | ![Compact InfoBadge Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/infobadge/navview-compact.png) | ![Top InfoBadge Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/infobadge/navview-top.png) | - -## Tab View - -The TabView control is a way to display a set of tabs and their respective content. TabViews are useful for displaying several pages (or documents) of content while giving a user the capability to rearrange, open, or close new tabs. [Learn more](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/tab-view) - -Here's an example of how to create a tab view: - -```dart -SizedBox( - height: 600, - child: TabView( - currentIndex: currentIndex, - onChanged: (index) => setState(() => currentIndex = index), - onNewPressed: () { - setState(() => tabs++); - }, - tabs: List.generate(tabs, (index) { - return Tab( - text: Text('Tab $index'), - closeIcon: FluentIcons.chrome_close, - ); - }), - bodies: List.generate( - tabs, - (index) => Container( - color: index.isEven ? Colors.red : Colors.yellow, - ), - ), - ), -), -``` - -The code above produces the following: - -![TabView Preview](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/tabview/tab-introduction.png) - ## Bottom Navigation The bottom navigation displays icons and optional text at the bottom of the screen for switching between different primary destinations in an app. This is commomly used on small screens. [Learn more](https://developer.microsoft.com/pt-br/fluentui#/controls/android/bottomnavigation) @@ -860,27 +660,6 @@ Which produces the following: ![InfoBar Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/infobar-default-title-message.png) -## Time Picker - -The time picker gives you a standardized way to let users pick a time value using touch, mouse, or keyboard input. [Learn more](https://docs.microsoft.com/en-us/windows/apps/design/controls/time-picker) - -Use a time picker to let a user pick a single time value. - -Here's an example of how to create a basic time picker: - -```dart -DateTime date = DateTime.now(); - -TimePicker( - header: 'Arrival time', - selected: date, - onChanged: (v) => setState(() => date = v), -), -``` - -The code above produces the following: - -![Time Picker Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/controls-timepicker-expand.gif) ## Progress Bar and Progress Ring @@ -1009,55 +788,6 @@ ComboBox( This will produce the same as the image above. -## TreeView - -The `TreeView` control enables a hierarchical list with expanding and collapsing nodes that contain nested items. It can be used to illustrate a folder structure or nested relationships in your UI. [Learn More](https://docs.microsoft.com/en-us/windows/apps/design/controls/tree-view) - -The tree view uses a combination of indentation and icons to represent the nested relationship between parent nodes and child nodes. Collapsed nodes use a chevron pointing to the right, and expanded nodes use a chevron pointing down. - -![TreeView Simple](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/treeview-simple.png) - -You can include an icon in the tree view item data template to represent nodes. For example, if you show a file system hierarchy, you could use folder icons for the parent notes and file icons for the leaf nodes. - -![TreeView Icons](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/treeview-icons.png) - -Each `TreeViewItem` can optionally take a `value` allowing you to store an arbitrary identifier with each item. This can be used in conjunction with `onSelectionChanged` to easily identify which items were selected without having to deconstruct the content widget. - -Here's an example of how to create a tree view: - -```dart -TreeView( - items: [ - TreeViewItem( - content: const Text('Work Documents'), - children: [ - TreeViewItem(content: const Text('XYZ Functional Spec')), - TreeViewItem(content: const Text('Feature Schedule')), - TreeViewItem(content: const Text('Overall Project Plan')), - TreeViewItem(content: const Text('Feature Resources Allocation')), - ], - ), - TreeViewItem( - content: const Text('Personal Documents'), - children: [ - TreeViewItem( - content: const Text('Home Remodel'), - children: [ - TreeViewItem(content: const Text('Contractor Contact Info')), - TreeViewItem(content: const Text('Paint Color Scheme')), - TreeViewItem(content: const Text('Flooring weedgrain type')), - TreeViewItem(content: const Text('Kitchen cabinet style')), - ], - ), - ], - ), - ], - onItemInvoked: (item) => debugPrint(item), // (optional) - // (optional). Can be TreeViewSelectionMode.single or TreeViewSelectionMode.multiple - selectionMode: TreeViewSelectionMode.none, -), -``` - ### Scrollable tree view Vertical scrolling can be enabled for a tree view by setting the `shrinkWrap` property to false. diff --git a/example/lib/main.dart b/example/lib/main.dart index 4e69e46f2..e1cbd86f8 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -19,6 +19,7 @@ import 'screens/inputs/button.dart'; import 'screens/inputs/checkbox.dart'; import 'screens/inputs/slider.dart'; import 'screens/inputs/toggle_switch.dart'; +import 'screens/navigation/navigation_view.dart'; import 'screens/navigation/tab_view.dart'; import 'screens/navigation/tree_view.dart'; import 'screens/settings.dart'; @@ -278,8 +279,7 @@ class _MyHomePageState extends State with WindowListener { TimePickerPage(), DatePickerPage(), // navigation - EmptyPage(), // navigation view - + NavigationViewPage(), TabViewPage(), TreeViewPage(), // surfaces diff --git a/example/lib/screens/navigation/navigation_view.dart b/example/lib/screens/navigation/navigation_view.dart new file mode 100644 index 000000000..a122e8aa5 --- /dev/null +++ b/example/lib/screens/navigation/navigation_view.dart @@ -0,0 +1,198 @@ +import 'package:example/widgets/card_highlight.dart'; +import 'package:fluent_ui/fluent_ui.dart'; + +import '../../widgets/page.dart'; + +class NavigationViewPage extends ScrollablePage { + @override + Widget buildHeader(BuildContext context) { + return const PageHeader(title: Text('NavigationView')); + } + + static const double itemHeight = 300.0; + + int topIndex = 0; + + @override + List buildScrollable(BuildContext context) { + return [ + const Text( + 'The NavigationView control provides top-level navigation for your app. ' + 'It adapts to a variety of screen sizes and supports both top and left ' + 'navigation styles.', + ), + subtitle(content: const Text('Top display mode')), + ...buildDisplayMode( + PaneDisplayMode.top, + 'The pane is positioned above the content.', + ), + subtitle(content: const Text('Open display mode')), + ...buildDisplayMode( + PaneDisplayMode.open, + 'The pane is expanded and positioned to the left of the content.', + ), + subtitle(content: const Text('Compact display mode')), + ...buildDisplayMode( + PaneDisplayMode.compact, + 'The pane shows only icons until opened and is positioned to the left ' + 'of the content. When opened, the pane overlays the content.', + ), + subtitle(content: const Text('Minimal display mode')), + ...buildDisplayMode( + PaneDisplayMode.minimal, + 'Only the menu button is shown until the pane is opened. When opened, ' + 'the pane overlays the left side of the content.', + ), + ]; + } + + List buildDisplayMode( + PaneDisplayMode displayMode, + String desc, + ) { + return [ + description(content: Text(desc)), + CardHighlight( + child: SizedBox( + height: itemHeight, + child: NavigationView( + appBar: const NavigationAppBar( + title: Text('NavigationView'), + ), + pane: NavigationPane( + selected: topIndex, + onChanged: (index) => setState(() => topIndex = index), + displayMode: displayMode, + items: [ + PaneItem( + icon: const Icon(FluentIcons.home), + title: const Text('Home'), + ), + PaneItem( + icon: const Icon(FluentIcons.issue_tracking), + title: const Text('Track orders'), + infoBadge: const InfoBadge(source: Text('8')), + ), + PaneItemExpander( + icon: const Icon(FluentIcons.account_management), + title: const Text('Account'), + items: [ + PaneItem( + icon: const Icon(FluentIcons.mail), + title: const Text('Mail'), + ), + PaneItem( + icon: const Icon(FluentIcons.calendar), + title: const Text('Calendar'), + ), + ], + ), + ], + footerItems: [ + PaneItem( + icon: const Icon(FluentIcons.settings), + title: const Text('Settings'), + ), + ], + ), + content: NavigationBody( + index: topIndex, + children: const [ + _NavigationBodyItem(), + _NavigationBodyItem( + header: 'Badging', + content: Text( + 'Badging is a non-intrusive and intuitive way to display ' + 'notifications or bring focus to an area within an app - ' + 'whether that be for notifications, indicating new content, ' + 'or showing an alert. An InfoBadge is a small piece of UI ' + 'that can be added into an app and customized to display a ' + 'number, icon, or a simple dot.', + ), + ), + _NavigationBodyItem( + header: 'PaneItemExpander', + content: Text( + 'Some apps may have a more complex hierarchical structure ' + 'that requires more than just a flat list of navigation ' + 'items. You may want to use top-level navigation items to ' + 'display categories of pages, with children items displaying ' + 'specific pages. It is also useful if you have hub-style ' + 'pages that only link to other pages. For these kinds of ' + 'cases, you should create a hierarchical NavigationView.', + ), + ), + _NavigationBodyItem(), + _NavigationBodyItem(), + _NavigationBodyItem(), + ], + ), + ), + ), + codeSnippet: '''NavigationView( + appBar: const NavigationAppBar( + title: Text('NavigationView'), + ), + pane: NavigationPane( + selected: topIndex, + onChanged: (index) => setState(() => topIndex = index), + displayMode: displayMode, + items: [ + PaneItem( + icon: const Icon(FluentIcons.home), + title: const Text('Home'), + ), + PaneItem( + icon: const Icon(FluentIcons.issue_tracking), + title: const Text('Track an order'), + infoBadge: const InfoBadge(source: Text('8')), + ), + PaneItemExpander( + icon: const Icon(FluentIcons.account_management), + title: const Text('Account'), + items: [ + PaneItem( + icon: const Icon(FluentIcons.mail), + title: const Text('Mail'), + ), + PaneItem( + icon: const Icon(FluentIcons.calendar), + title: const Text('Calendar'), + ), + ], + ), + ], + ), + body: NavigationBody( + index: topIndex, + children: const [ + BodyItem(), + BodyItem(), + BodyItem(), + BodyItem(), + ], + ) +)''', + ), + ]; + } +} + +class _NavigationBodyItem extends StatelessWidget { + const _NavigationBodyItem({ + Key? key, + this.header, + this.content, + }) : super(key: key); + + final String? header; + final Widget? content; + + @override + Widget build(BuildContext context) { + return ScaffoldPage.withPadding( + header: PageHeader(title: Text(header ?? 'This is a header text')), + content: content ?? const SizedBox.shrink(), + ); + } +} diff --git a/example/lib/screens/navigation/tab_view.dart b/example/lib/screens/navigation/tab_view.dart index 31e5cdc60..d2eec3285 100644 --- a/example/lib/screens/navigation/tab_view.dart +++ b/example/lib/screens/navigation/tab_view.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:example/widgets/card_highlight.dart'; import 'package:example/widgets/page.dart'; import 'package:fluent_ui/fluent_ui.dart'; @@ -44,7 +45,10 @@ class TabViewPage extends ScrollablePage { tabs ??= List.generate(3, generateTab); return [ const Text( - 'A control that displays a collection of tabs that can be used to display several documents.', + 'The TabView control is a way to display a set of tabs and their ' + 'respective content. TabViews are useful for displaying several pages ' + '(or documents) of content while giving a user the capability to ' + 'rearrange, open, or close new tabs.', ), subtitle( content: const Text( @@ -112,7 +116,7 @@ class TabViewPage extends ScrollablePage { ], ), ), - Card( + CardHighlight( child: SizedBox( height: 400, child: TabView( @@ -147,6 +151,8 @@ class TabViewPage extends ScrollablePage { }, ), ), + // TODO: TabView snippets + codeSnippet: '''''', ), ]; } diff --git a/example/lib/screens/navigation/tree_view.dart b/example/lib/screens/navigation/tree_view.dart index fc591c452..4f511fa9a 100644 --- a/example/lib/screens/navigation/tree_view.dart +++ b/example/lib/screens/navigation/tree_view.dart @@ -1,3 +1,4 @@ +import 'package:example/widgets/card_highlight.dart'; import 'package:example/widgets/page.dart'; import 'package:fluent_ui/fluent_ui.dart'; @@ -11,10 +12,16 @@ class TreeViewPage extends ScrollablePage { List buildScrollable(BuildContext context) { return [ const Text( - 'The TreeView control is a hierarchical list pattern with expanding and collapsing nodes that contain nested items.', + 'The tree view control enables a hierarchical list with expanding and ' + 'collapsing nodes that contain nested items. It can be used to ' + 'illustrate a folder structure or nested relationships in your UI.\n\n' + 'The tree view uses a combination of indentation and icons to represent ' + 'the nested relationship between parent nodes and child nodes. Collapsed ' + 'nodes use a chevron pointing to the right, and expanded nodes use a ' + 'chevron pointing down.', ), subtitle(content: const Text('A TreeView with Multi-selection enabled')), - Card( + CardHighlight( child: TreeView( selectionMode: TreeViewSelectionMode.multiple, shrinkWrap: true, @@ -23,37 +30,136 @@ class TreeViewPage extends ScrollablePage { onSelectionChanged: (selectedItems) async => debugPrint( 'onSelectionChanged: ${selectedItems.map((i) => i.value)}'), ), + codeSnippet: '''final items = [ + TreeViewItem( + content: const Text('Personal Documents'), + value: 'personal_docs', + children: [ + TreeViewItem( + content: const Text('Home Remodel'), + value: 'home_remodel', + children: [ + TreeViewItem( + content: const Text('Contractor Contact Info'), + value: 'contr_cont_inf', + ), + TreeViewItem( + content: const Text('Paint Color Scheme'), + value: 'paint_color_scheme', + ), + TreeViewItem( + content: const Text('Flooring weedgrain type'), + value: 'flooring_weedgrain_type', + ), + TreeViewItem( + content: const Text('Kitchen cabinet style'), + value: 'kitch_cabinet_style', + ), + ], + ), + TreeViewItem( + content: const Text('Tax Documents'), + value: 'tax_docs', + children: [ + TreeViewItem(content: const Text('2017'), value: "tax_2017"), + TreeViewItem( + content: const Text('Middle Years'), + value: 'tax_middle_years', + children: [ + TreeViewItem(content: const Text('2018'), value: "tax_2018"), + TreeViewItem(content: const Text('2019'), value: "tax_2019"), + TreeViewItem(content: const Text('2020'), value: "tax_2020"), + ], + ), + TreeViewItem(content: const Text('2021'), value: "tax_2021"), + TreeViewItem(content: const Text('Current Year'), value: "tax_cur"), + ], + ), + ], + ), +]; + +TreeView( + selectionMode: TreeViewSelectionMode.multiple, + shrinkWrap: true, + items: items, + onItemInvoked: (item) async => debugPrint('onItemInvoked: \$item'), + onSelectionChanged: (selectedItems) async => debugPrint( + 'onSelectionChanged: \${selectedItems.map((i) => i.value)}'), +) +''', + ), + subtitle(content: const Text('A TreeView with lazy-loading items')), + CardHighlight( + child: TreeView( + selectionMode: TreeViewSelectionMode.multiple, + shrinkWrap: true, + items: lazyItems, + onItemInvoked: (item) async => debugPrint('onItemInvoked: $item'), + onSelectionChanged: (selectedItems) async => debugPrint( + 'onSelectionChanged: ${selectedItems.map((i) => i.value)}'), + ), + codeSnippet: '''final lazyItems = [ + TreeViewItem( + content: const Text('Work Documents'), + value: 'work_docs', + // this means the item children will futurely be inserted + lazy: true, + // ensure the list is modifiable + children: [], + onInvoked: (item) async { + // if it's already populated, return + // we don't want to populate it twice + if (item.children.isNotEmpty) return; + // mark as loading + setState(() => item.loading = true); + // do your fetching... + await Future.delayed(const Duration(seconds: 2)); + setState(() { + item + // set loading to false + ..loading = false + // add the fetched nodes + ..children.addAll([ + TreeViewItem( + content: const Text('XYZ Functional Spec'), + value: 'xyz_functional_spec', + ), + TreeViewItem( + content: const Text('Feature Schedule'), + value: 'feature_schedule', + ), + TreeViewItem( + content: const Text('Overall Project Plan'), + value: 'overall_project_plan', + ), + TreeViewItem( + content: const Text( + 'Feature Resources Allocation (this text should not overflow)', + overflow: TextOverflow.ellipsis, + ), + value: 'feature_resources_alloc', + ), + ]); + }); + }, + ), +]; + +TreeView( + selectionMode: TreeViewSelectionMode.multiple, + shrinkWrap: true, + items: lazyItems, + onItemInvoked: (item) async => debugPrint('onItemInvoked: \$item'), + onSelectionChanged: (selectedItems) async => debugPrint( + 'onSelectionChanged: \${selectedItems.map((i) => i.value)}'), +) +''', ), ]; } - final items = [ - TreeViewItem( - content: const Text('Work Documents'), - value: 'work_docs', - lazy: true, - children: [ - TreeViewItem( - content: const Text('XYZ Functional Spec'), - value: 'xyz_functional_spec', - ), - TreeViewItem( - content: const Text('Feature Schedule'), - value: 'feature_schedule', - ), - TreeViewItem( - content: const Text('Overall Project Plan'), - value: 'overall_project_plan', - ), - TreeViewItem( - content: const Text( - 'Feature Resources Allocation (this text should not overflow)', - overflow: TextOverflow.ellipsis, - ), - value: 'feature_resources_alloc', - ), - ], - ), + late final items = [ TreeViewItem( content: const Text('Personal Documents'), value: 'personal_docs', @@ -101,4 +207,50 @@ class TreeViewPage extends ScrollablePage { ], ), ]; + + late final lazyItems = [ + TreeViewItem( + content: const Text('Work Documents'), + value: 'work_docs', + // this means the item will be expandable + lazy: true, + // ensure the list is modifiable + children: [], + onInvoked: (item) async { + // if it's already populated, return + if (item.children.isNotEmpty) return; + // mark as loading + setState(() => item.loading = true); + // do your fetching... + await Future.delayed(const Duration(seconds: 2)); + setState(() { + item + // set loading to false + ..loading = false + // add the fetched nodes + ..children.addAll([ + TreeViewItem( + content: const Text('XYZ Functional Spec'), + value: 'xyz_functional_spec', + ), + TreeViewItem( + content: const Text('Feature Schedule'), + value: 'feature_schedule', + ), + TreeViewItem( + content: const Text('Overall Project Plan'), + value: 'overall_project_plan', + ), + TreeViewItem( + content: const Text( + 'Feature Resources Allocation (this text should not overflow)', + overflow: TextOverflow.ellipsis, + ), + value: 'feature_resources_alloc', + ), + ]); + }); + }, + ), + ]; } diff --git a/example/lib/widgets/card_highlight.dart b/example/lib/widgets/card_highlight.dart index 7deaa6441..f8efb3709 100644 --- a/example/lib/widgets/card_highlight.dart +++ b/example/lib/widgets/card_highlight.dart @@ -50,9 +50,11 @@ class _CardHighlightState extends State { ), onStateChanged: (state) { Future.delayed(Duration.zero, () { - setState(() { - isOpen = state; - }); + if (mounted) { + setState(() { + isOpen = state; + }); + } }); }, trailing: isOpen diff --git a/lib/src/controls/navigation/navigation_view/pane_items.dart b/lib/src/controls/navigation/navigation_view/pane_items.dart index d8d0fb529..3f6a3e437 100644 --- a/lib/src/controls/navigation/navigation_view/pane_items.dart +++ b/lib/src/controls/navigation/navigation_view/pane_items.dart @@ -797,9 +797,13 @@ class _PaneItemExpanderMenuItem extends MenuFlyoutItemInterface { borderRadius: BorderRadius.circular(6.0), ), child: Row(mainAxisSize: MainAxisSize.min, children: [ + Padding( + padding: const EdgeInsetsDirectional.only(end: 12.0), + child: item.icon, + ), Flexible( fit: size.isEmpty ? FlexFit.loose : FlexFit.tight, - child: item.title ?? item.icon, + child: item.title ?? const SizedBox.shrink(), ), if (item.infoBadge != null) Padding( diff --git a/lib/src/controls/navigation/navigation_view/view.dart b/lib/src/controls/navigation/navigation_view/view.dart index 0bb48f76f..0986e5381 100644 --- a/lib/src/controls/navigation/navigation_view/view.dart +++ b/lib/src/controls/navigation/navigation_view/view.dart @@ -724,18 +724,21 @@ class _NavigationAppBar extends StatelessWidget { leading, if (additionalLeading != null) additionalLeading!, title, - if (appBar.actions != null) Expanded(child: appBar.actions!), + if (appBar.actions != null) Expanded(child: appBar.actions!) ]); break; case PaneDisplayMode.minimal: case PaneDisplayMode.open: case PaneDisplayMode.compact: result = Stack(children: [ - Row(mainAxisSize: MainAxisSize.min, children: [ - leading, - if (additionalLeading != null) additionalLeading!, - Flexible(child: title), - ]), + Align( + alignment: Alignment.centerLeft, + child: Row(mainAxisSize: MainAxisSize.min, children: [ + leading, + if (additionalLeading != null) additionalLeading!, + Flexible(child: title), + ]), + ), if (appBar.actions != null) Positioned.directional( textDirection: direction, diff --git a/lib/src/controls/utils/info_badge.dart b/lib/src/controls/utils/info_badge.dart index ed5edf90e..87d335e99 100644 --- a/lib/src/controls/utils/info_badge.dart +++ b/lib/src/controls/utils/info_badge.dart @@ -1,12 +1,23 @@ import 'package:fluent_ui/fluent_ui.dart'; -/// An InfoBadge is a small piece of UI that can be added -/// into an app and customized to display a number, icon, -/// or a simple dot. +/// Badging is a non-intrusive and intuitive way to display notifications or +/// bring focus to an area within an app - whether that be for notifications, +/// indicating new content, or showing an alert. An `InfoBadge` is a small +/// piece of UI that can be added into an app and customized to display a number, +/// icon, or a simple dot. +/// +/// ![InfoBadge Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/infobadge/infobadge-example-1.png) +/// +/// [InfoBadge] is built into [NavigationView], but can also be placed as a +/// standalone widget, allowing you to place [InfoBadge] into any control or +/// piece of UI of your choosing. When you use an [InfoBadge] somewhere other +/// than [NavigationView], you are responsible for programmatically determining +/// when to show and dismiss the [InfoBadge], and where to place the [InfoBadge]. /// /// Learn more: /// /// * +/// * [NavigationView], which provides top-level navigation for your app class InfoBadge extends StatelessWidget { /// Creates an info badge. const InfoBadge({ From 228bc0f303b66027a523b9e875993b3f6c456cc2 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Fri, 26 Aug 2022 15:07:34 -0300 Subject: [PATCH 4/7] Update surface/ documentation --- README.md | 230 ------------------ example/lib/screens/home.dart | 11 +- .../screens/navigation/navigation_view.dart | 77 +++++- example/lib/screens/surface/acrylic.dart | 48 +++- .../lib/screens/surface/content_dialog.dart | 5 +- example/lib/screens/surface/expander.dart | 124 +++++++++- example/lib/screens/surface/info_bars.dart | 12 + .../screens/surface/progress_indicators.dart | 28 ++- example/lib/screens/surface/tiles.dart | 17 +- example/lib/widgets/card_highlight.dart | 7 +- lib/src/controls/surfaces/flyout/content.dart | 9 +- lib/src/controls/surfaces/tooltip.dart | 16 +- lib/src/utils/label.dart | 28 ++- test/label_test.dart | 2 +- 14 files changed, 325 insertions(+), 289 deletions(-) diff --git a/README.md b/README.md index ef173fadc..37a93908b 100644 --- a/README.md +++ b/README.md @@ -52,14 +52,7 @@ - [Font](#font) - [Type ramp](#type-ramp) - [Reveal Focus](#reveal-focus) -- [Motion](#motion) - - [Page Transitions](#page-transitions) - [Navigation](#navigation) - - [Navigation View](#navigation-view) - - [App Bar](#app-bar) - - [Navigation Pane](#navigation-pane) - - [Navigation Body](#navigation-body) - - [Info Badge](#info-badge) - [Bottom Navigation](#bottom-navigation) - [Widgets](#widgets) - [Tooltip](#tooltip) @@ -369,42 +362,6 @@ FocusTheme( ), ``` -# Motion - -This package widely uses animation in the widgets. The animation duration and curve can be defined on the app theme. - -## Page transitions - -Page transitions navigate users between pages in an app, providing feedback as the relationship between pages. Page transitions help users understand if they are at the top of a navigation hierarchy, moving between sibling pages, or navigating deeper into the page hierarchy. - -It's recommended to widely use page transitions on `NavigationView`, that can be implemented using the widget `NavigationBody`. - -This library gives you the following implementations to navigate between your pages: - -### Entrance - -Entrance is a combination of a slide up animation and a fade in animation for the incoming content. Use entrance when the user is taken to the top of a navigational stack, such as navigating between tabs or left-nav items. - -The desired feeling is that the user has started over. - -Avaiable with the widget `EntrancePageTransition`, it produces the following effect: - -![Entrance Page Transition Preview](https://docs.microsoft.com/en-us/windows/uwp/design/motion/images/page-refresh.gif) - -### Drill In - -Use drill when users navigate deeper into an app, such as displaying more information after selecting an item. - -The desired feeling is that the user has gone deeper into the app. - -Avaiable with the widget `DrillInPageTransition`, it produces the following effect: - -![Drill Page Transition Preview](https://docs.microsoft.com/en-us/windows/uwp/design/motion/images/drill.gif) - -### Horizontal - -It's avaiable with the widget `HorizontalSlidePageTransition`. - # Navigation The default Flutter Navigation is available on the `FluentApp` widget, that means you can simply call `Navigator.push` and `Navigator.pop` to navigate between routes. See [navigate to a new screen and back](https://flutter.dev/docs/cookbook/navigation/navigation-basics) @@ -477,104 +434,6 @@ You can set `amount` to change the amount of stars. The `rating` must be less th # Widgets -## Tooltip - -A tooltip is a short description that is linked to another control or object. Tooltips help users understand unfamiliar objects that aren't described directly in the UI. They display automatically when the user moves focus to, presses and holds, or hovers the mouse pointer over a control. The tooltip disappears after a few seconds, or when the user moves the finger, pointer or keyboard/gamepad focus. [Learn more](https://docs.microsoft.com/en-us/windows/apps/design/controls/tooltips) - -To add a tooltip to a widget, wrap it in a `Tooltip` widget: - -```dart -Tooltip( - message: 'Click to perform an action', - child: Button( - child: Text('Button with tooltip'), - onPressed: () { - print('Pressed button with tooltip'); - } - ), -) -``` - -It's located above or below the `child` widget. You can specify the preffered location when both locations are available using the `preferBelow` property. - -![Tooltip Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/controls/tool-tip.png) - -## Content Dialog - -Dialogs are modal UI overlays that provide contextual app information. They block interactions with the app window until being explicitly dismissed. They often request some kind of action from the user. [Learn more](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/dialogs-and-flyouts/dialogs) - -You can create a Dialog with the widget `ContentDialog`: - -```dart -ContentDialog( - title: Text('No WiFi connection'), - content: Text('Check your connection and try again'), - actions: [ - Button( - child: Text('Ok'), - onPressed: () { - Navigator.pop(context); - } - ) - ], -), -``` - -The code above produces the following: - -![No Wifi Connection Dialog](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/dialogs/dialog_rs2_one_button.png) - -You can display the dialog as an overlay by calling the function `showDialog`: - -```dart -showDialog( - context: context, - builder: (context) { - return ContentDialog(...); - }, -); -``` - -![Delete File Dialog](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/dialogs/dialog_rs2_delete_file.png)\ -![Subscribe to App Service Dialog](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/dialogs/dialog_rs2_three_button_default.png)\ - -## Expander - -Expander lets you show or hide less important content that's related to a piece of primary content that's always visible. Items contained in the `header` are always visible. The user can expand and collapse the `content` area, where secondary content is displayed, by interacting with the header. When the content area is expanded, it pushes other UI elements out of the way; it does not overlay other UI. The Expander can expand upwards or downwards. - -Both the `header` and `content` areas can contain any content, from simple text to complex UI layouts. For example, you can use the control to show additional options for an item. - -![Expander](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/expander-default.gif) - -Use an Expander when some primary content should always be visible, but related secondary content may be hidden until needed. This UI is commonly used when display space is limited and when information or options can be grouped together. Hiding the secondary content until it's needed can also help to focus the user on the most important parts of your app. - -Here's an example of how to create an expander: - -```dart -Expander( - header: const Text('This thext is in header'), - content: const Text('This is the content'), - direction: ExpanderDirection.down, // (optional). Defaults to ExpanderDirection.down - initiallyExpanded: false, // (false). Defaults to false -), -``` - -Open and close the expander programatically: - -```dart -final _expanderKey = GlobalKey(); - -Expander( - header: const Text('This thext is in header'), - content: const Text('This is the content'), -), - -// Call this function to close the expander -void close() { - _expanderKey.currentState?.open = false; -} -``` - ## Flyout A flyout is a light dismiss container that can show arbitrary UI as its content. Flyouts can contain other flyouts or context menus to create a nested experience. @@ -604,95 +463,6 @@ void dispose() { } ``` -## Acrylic - -Acrylic is a type of Brush that creates a translucent texture. You can apply acrylic to app surfaces to add depth and help establish a visual hierarchy. [Learn more](https://docs.microsoft.com/en-us/windows/uwp/design/style/acrylic) - -![Acrylic](https://docs.microsoft.com/en-us/windows/uwp/design/style/images/header-acrylic.svg) - -| Do | Don't | -| ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Do use acrylic as the background material of non-primary app surfaces like navigation panes. | Don't put desktop acrylic on large background surfaces of your app - this breaks the mental model of acrylic being used primarily for transient surfaces. | -| Do extend acrylic to at least one edge of your app to provide a seamless experience by subtly blending with the app’s surroundings. | Don’t place in-app and background acrylics directly adjacent to avoid visual tension at the seams. | -| | Don't place multiple acrylic panes with the same tint and opacity next to each other because this results in an undesirable visible seam. | -| | Don’t place accent-colored text over acrylic surfaces. | - -```dart -SizedBox( - height: ..., - width: ..., - child: Acrylic( - child: Button( - child: Text('Mom it\'s me hehe <3'), - onPressed: () { - print('button inside acrylic pressed'); - } - ), - ), -), -``` - -![Acrylic preview](https://docs.microsoft.com/en-us/windows/uwp/design/style/images/luminosityversustint.png) - -## InfoBar - -The `InfoBar` control is for displaying app-wide status messages to users that are highly visible yet non-intrusive. There are built-in Severity levels to easily indicate the type of message shown as well as the option to include your own call to action or hyperlink button. Since the InfoBar is inline with other UI content the option is there for the control to always be visible or dismissed by the user. - -You can easility create it using the `InfoBar` widget and theme it using `InfoBarThemeData`. It has built-in support for both light and dark theme: - -```dart -bool _visible = true; - -if (_visible) - InfoBar( - title: Text('Update available'), - content: Text('Restart the app to apply the latest update.'), // optional - severity: InfoBarSeverity.info, // optional. Default to InfoBarSeverity.info - onClose: () { - // Dismiss the info bar - setState(() => _visible = false); - } - ), -``` - -Which produces the following: - -![InfoBar Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/infobar-default-title-message.png) - - - -## Progress Bar and Progress Ring - -A progress control provides feedback to the user that a long-running operation is underway. It can mean that the user cannot interact with the app when the progress indicator is visible, and can also indicate how long the wait time might be, depending on the indicator used. - -Here's an example of how to create a ProgressBar: - -```dart -ProgressBar(value: 35) -``` - -![Determinate Progress Bar](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/progressbar-determinate.png) - -You can omit the `value` property to create an indeterminate progress bar: - -![Indeterminate Progress Bar](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/progressbar-indeterminate.gif) - -Indeterminate Progress Bar is a courtesy of [@raitonubero](https://github.com/raitonoberu). Show him some love - -Here's an example of how to create a progress ring: - -```dart -ProgressRing(value: 35) -``` - -![Determinate Progress Ring](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/progress_ring.jpg) - -You can omit the `value` property to create an indeterminate progress ring: - -![Indeterminate Progress Ring](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/progressring-indeterminate.gif) - -Both Indeterminate ProgressBar and Indeterminate ProgressRing is a courtesy of [@raitonubero](https://github.com/raitonoberu). Show him some love ❤ - ## Scrollbar A scrollbar thumb indicates which portion of a [ScrollView] is actually visible. [Learn more](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/scroll-controls) diff --git a/example/lib/screens/home.dart b/example/lib/screens/home.dart index 9ba266c2c..da1d0ac74 100644 --- a/example/lib/screens/home.dart +++ b/example/lib/screens/home.dart @@ -60,12 +60,13 @@ class HomePage extends ScrollablePage { ), ), ), - const RepaintBoundary( + RepaintBoundary( child: Padding( - padding: EdgeInsetsDirectional.only(start: 4.0), + padding: const EdgeInsetsDirectional.only(start: 4.0), child: InfoLabel( label: 'Progress', - child: SizedBox(height: 30, width: 30, child: ProgressRing()), + child: const SizedBox( + height: 30, width: 30, child: ProgressRing()), ), ), ), @@ -88,9 +89,9 @@ class HomePage extends ScrollablePage { ]), ), ), - const InfoLabel( + InfoLabel( label: 'Icons', - child: Icon(FluentIcons.graph_symbol, size: 30.0), + child: const Icon(FluentIcons.graph_symbol, size: 30.0), ), InfoLabel( label: 'Colors', diff --git a/example/lib/screens/navigation/navigation_view.dart b/example/lib/screens/navigation/navigation_view.dart index a122e8aa5..6fd2a8fe4 100644 --- a/example/lib/screens/navigation/navigation_view.dart +++ b/example/lib/screens/navigation/navigation_view.dart @@ -13,6 +13,15 @@ class NavigationViewPage extends ScrollablePage { int topIndex = 0; + PaneDisplayMode displayMode = PaneDisplayMode.open; + String pageTransition = 'default'; + static const List pageTransitions = [ + 'default', + 'entrance', + 'drill in', + 'horizontal', + ]; + @override List buildScrollable(BuildContext context) { return [ @@ -21,36 +30,67 @@ class NavigationViewPage extends ScrollablePage { 'It adapts to a variety of screen sizes and supports both top and left ' 'navigation styles.', ), - subtitle(content: const Text('Top display mode')), + const SizedBox(height: 10.0), ...buildDisplayMode( PaneDisplayMode.top, + 'Top display mode', 'The pane is positioned above the content.', ), - subtitle(content: const Text('Open display mode')), ...buildDisplayMode( PaneDisplayMode.open, + 'Open display mode', 'The pane is expanded and positioned to the left of the content.', ), - subtitle(content: const Text('Compact display mode')), ...buildDisplayMode( PaneDisplayMode.compact, + 'Compact display mode', 'The pane shows only icons until opened and is positioned to the left ' - 'of the content. When opened, the pane overlays the content.', + 'of the content. When opened, the pane overlays the content.', ), - subtitle(content: const Text('Minimal display mode')), ...buildDisplayMode( PaneDisplayMode.minimal, + 'Minimal display mode', 'Only the menu button is shown until the pane is opened. When opened, ' - 'the pane overlays the left side of the content.', + 'the pane overlays the left side of the content.', ), ]; } List buildDisplayMode( PaneDisplayMode displayMode, + String title, String desc, ) { + if (displayMode != this.displayMode) return []; return [ + Wrap(runSpacing: 10.0, spacing: 10.0, children: [ + InfoLabel( + label: 'Display mode', + child: ComboBox( + value: displayMode, + items: ([...PaneDisplayMode.values]..remove(PaneDisplayMode.auto)) + .map((mode) { + return ComboboxItem(child: Text(mode.name), value: mode); + }).toList(), + onChanged: (mode) => setState( + () => this.displayMode = mode ?? displayMode, + ), + ), + ), + InfoLabel( + label: 'Page Transition', + child: ComboBox( + items: pageTransitions + .map((e) => ComboboxItem(child: Text(e), value: e)) + .toList(), + value: pageTransition, + onChanged: (transition) => setState( + () => pageTransition = transition ?? pageTransition, + ), + ), + ), + ]), + subtitle(content: Text(title)), description(content: Text(desc)), CardHighlight( child: SizedBox( @@ -97,6 +137,31 @@ class NavigationViewPage extends ScrollablePage { ), content: NavigationBody( index: topIndex, + transitionBuilder: pageTransition == 'default' + ? null + : (child, animation) { + switch (pageTransition) { + case 'entrance': + return EntrancePageTransition( + child: child, + animation: animation, + ); + case 'drill in': + return DrillInPageTransition( + child: child, + animation: animation, + ); + case 'horizontal': + return HorizontalSlidePageTransition( + child: child, + animation: animation, + ); + default: + throw UnsupportedError( + '$pageTransition is not a supported transition', + ); + } + }, children: const [ _NavigationBodyItem(), _NavigationBodyItem( diff --git a/example/lib/screens/surface/acrylic.dart b/example/lib/screens/surface/acrylic.dart index 3afa18448..af5da018f 100644 --- a/example/lib/screens/surface/acrylic.dart +++ b/example/lib/screens/surface/acrylic.dart @@ -3,6 +3,26 @@ import 'package:fluent_ui/fluent_ui.dart'; import '../settings.dart'; +const questionMark = Padding( + padding: EdgeInsetsDirectional.only(start: 4.0), + child: Icon(FluentIcons.status_circle_question_mark, size: 14.0), +); + +InlineSpan _buildLabel(String label, String description) { + return TextSpan( + text: label, + children: [ + WidgetSpan( + child: Tooltip( + useMousePosition: false, + message: description, + child: questionMark, + ), + ), + ], + ); +} + class AcrylicPage extends ScrollablePage { @override Widget buildHeader(BuildContext context) { @@ -18,7 +38,11 @@ class AcrylicPage extends ScrollablePage { @override List buildScrollable(BuildContext context) { return [ - const Text('A translucent material recommended for panel backgrounds.'), + const Text( + 'A translucent material recommended for panel backgrounds. Acrylic is a ' + 'type of Brush that creates a translucent texture. You can apply acrylic ' + 'to app surfaces to add depth and help establish a visual hierarchy.', + ), subtitle(content: const Text('Default background acrylic brush.')), Card( child: SizedBox( @@ -59,8 +83,11 @@ class AcrylicPage extends ScrollablePage { ]), ), Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - InfoLabel( - label: 'Tint color', + InfoLabel.rich( + label: _buildLabel( + 'Tint color', + 'the color/tint overlay layer.', + ), child: ComboBox( placeholder: const Text('Tint color '), onChanged: (c) => setState(() => color = c), @@ -96,8 +123,11 @@ class AcrylicPage extends ScrollablePage { ], ), ), - InfoLabel( - label: 'Tint opacity', + InfoLabel.rich( + label: _buildLabel( + 'Tint opacity', + 'the opacity of the tint layer.', + ), child: Slider( value: tintOpacity, min: 0.0, @@ -105,8 +135,12 @@ class AcrylicPage extends ScrollablePage { onChanged: (v) => setState(() => tintOpacity = v), ), ), - InfoLabel( - label: 'Luminosity opacity', + InfoLabel.rich( + label: _buildLabel( + 'Tint luminosity opacity', + 'controls the amount of saturation that is allowed through ' + 'the acrylic surface from the background.', + ), child: Slider( value: luminosityOpacity, min: 0.0, diff --git a/example/lib/screens/surface/content_dialog.dart b/example/lib/screens/surface/content_dialog.dart index 870e7e9fa..afcd67558 100644 --- a/example/lib/screens/surface/content_dialog.dart +++ b/example/lib/screens/surface/content_dialog.dart @@ -14,7 +14,10 @@ class ContentDialogPage extends ScrollablePage { List buildScrollable(BuildContext context) { return [ const Text( - 'Use a ContentDialog to show relavant information or to provide a modal dialog experience that can show any content.', + 'Dialog controls are modal UI overlays that provide contextual app ' + 'information. They block interactions with the app window until being ' + 'explicitly dismissed. They often request some kind of action from the ' + 'user.', ), subtitle(content: const Text('A basic content dialog with content')), CardHighlight( diff --git a/example/lib/screens/surface/expander.dart b/example/lib/screens/surface/expander.dart index a05713fd0..a9415c5a8 100644 --- a/example/lib/screens/surface/expander.dart +++ b/example/lib/screens/surface/expander.dart @@ -11,22 +11,136 @@ class ExpanderPage extends ScrollablePage { final expanderKey = GlobalKey(); + bool crostOpen = false; + List crosts = [ + 'Classic', + 'Whole wheat', + 'Gluten free', + ]; + String crost = 'Whole wheat'; + List sizes = [ + 'Regular', + 'Thin', + 'Pan', + 'Stuffed', + ]; + String size = 'Pan'; + bool checked = false; + @override List buildScrollable(BuildContext context) { final open = expanderKey.currentState?.open ?? false; return [ - const Text( - 'The Expander has a header and can expand to show a body with more content. Use an Expander when some content is only relevant some of the time (for example to read more information or access additional options for an item).', + description( + content: const Text( + 'The Expander control lets you show or hide less important content ' + 'that\'s related to a piece of primary content that\'s always visible. ' + 'Items contained in the Header are always visible. The user can expand ' + 'and collapse the Content area, where secondary content is displayed, ' + 'by interacting with the header. When the content area is expanded, it ' + 'pushes other UI elements out of the way; it does not overlay other UI. ' + 'The Expander can expand upwards or downwards.\n\n' + 'Both the Header and Content areas can contain any content, from simple ' + 'text to complex UI layouts. For example, you can use the control to ' + 'show additional options for an item.\n\n' + 'Use an Expander when some primary content should always be visible, ' + 'but related secondary content may be hidden until needed. This UI is ' + 'commonly used when display space is limited and when information or ' + 'options can be grouped together. Hiding the secondary content until ' + 'it\'s needed can also help to focus the user on the most important ' + 'parts of your app.', + ), ), subtitle(content: const Text('Simple expander')), - const CardHighlight( + description( + content: const Text( + 'In this example, the trailing vanishes when the expander is open.', + ), + ), + CardHighlight( child: Expander( - header: Text('This text is in header'), - content: Text('This text is in content'), + header: const Text('Choose your crost'), + onStateChanged: (open) => setState(() => crostOpen = open), + trailing: crostOpen + ? null + : Text( + '$crost, $size', + style: FluentTheme.of(context).typography.caption, + ), + content: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: crosts + .map( + (e) => Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: RadioButton( + checked: crost == e, + onChanged: (selected) { + if (selected) setState(() => crost = e); + }, + content: Text(e), + ), + ), + ) + .toList(), + ), + const SizedBox(width: 12.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: sizes + .map((e) => Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: RadioButton( + checked: size == e, + onChanged: (selected) { + if (selected) setState(() => size = e); + }, + content: Text(e), + ), + )) + .toList(), + ), + ]), ), codeSnippet: '''Expander( + leading: RadioButton( + checked: checked, + onChanged: (v) => setState(() => checked = v), + ), header: Text('This text is in header'), content: Text('This text is in content'), +)''', + ), + subtitle(content: const Text('Scrollable content')), + const CardHighlight( + child: Expander( + header: Text('Open to see the scrollable text'), + content: SizedBox( + height: 300, + child: SingleChildScrollView( + child: SelectableText( + '''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis porta lectus lacus, ut viverra ex aliquet at. Sed ac tempus magna. Ut velit diam, condimentum ac bibendum sit amet, aliquam at quam. Mauris bibendum, elit ut mollis molestie, neque risus lacinia libero, id fringilla lacus odio a nisl. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Aliquam viverra tincidunt diam, id porta justo iaculis ac. Aenean ornare bibendum rutrum. Aenean dignissim egestas augue id elementum. Suspendisse dapibus, felis nec varius porta, purus turpis sodales est, sit amet consectetur velit turpis in orci. Curabitur sed tortor purus. Donec ut ligula tortor. Quisque ac nulla dui. Praesent sed diam id dui pharetra facilisis. Maecenas lacinia augue eu metus luctus, vitae efficitur ex accumsan. Sed viverra tellus quis ex tempus, sit amet aliquam mauris hendrerit. Proin tempus nisl mauris, eget ultricies ligula aliquet id. + +Fusce molestie quis augue vel eleifend. Praesent ligula velit, porta id diam sed, malesuada molestie odio. Proin egestas nisl vel leo accumsan, vel ullamcorper ipsum dapibus. Curabitur libero augue, porttitor dictum mauris ut, dignissim blandit lacus. Suspendisse lacinia augue elit, sit amet auctor eros pretium sit amet. Proin ullamcorper augue nulla, sit amet rhoncus nisl gravida ac. Aenean auctor ligula in nibh fermentum fermentum. Aliquam erat volutpat. Sed molestie vulputate diam, id rhoncus augue mattis vitae. Ut tempus tempus dui, in imperdiet elit tincidunt id. Integer congue urna eu nisl bibendum accumsan. Aliquam commodo tempor turpis sit amet suscipit. + +Donec sit amet semper sem. Pellentesque commodo mi in est sagittis ultricies in ut elit. Donec vulputate commodo vestibulum. Pellentesque pulvinar tortor vel suscipit hendrerit. Vestibulum interdum, est et aliquam dapibus, tellus elit pharetra nisl, in volutpat sapien ipsum in velit. Donec gravida erat tellus, et molestie diam interdum sed. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Duis dolor nisl, viverra sed pellentesque molestie, tempor ut enim. Duis suscipit massa sed dolor suscipit mollis. In lobortis efficitur egestas. Integer blandit, dolor eu tristique mollis, lorem urna convallis arcu, non iaculis elit purus vel velit. Nam eget aliquet quam, vitae luctus urna. Nunc vehicula sagittis risus, vitae pretium lacus ornare semper. In ornare massa vitae odio consequat, eu lacinia mi imperdiet. Vivamus at augue diam. Fusce eu purus magna. + +Fusce tempor, dolor in porttitor porttitor, turpis leo ullamcorper urna, vitae ultrices lorem augue eget nulla. Nulla sodales venenatis tellus quis feugiat. Phasellus sit amet condimentum nulla. Quisque felis lorem, tempus quis odio id, tincidunt volutpat ante. Fusce ultrices dui vel lorem tincidunt, in pellentesque ligula luctus. Morbi luctus est vitae eros blandit dictum. Quisque convallis diam sed arcu volutpat, eget placerat turpis cursus. Aliquam dapibus finibus luctus. Praesent vestibulum viverra risus, nec sollicitudin mi mattis eu. Nulla vestibulum, nibh eget sagittis placerat, elit eros egestas libero, eu luctus justo ante eget tellus. Etiam quis lacus gravida, consequat diam in, laoreet sapien. + +Fusce nunc neque, imperdiet id justo non, porttitor finibus massa. Ut quis risus quis tellus ultricies accumsan et et lorem. Nam pulvinar luctus velit, ut vehicula neque sagittis nec. Integer commodo, metus auctor rutrum finibus, tellus justo feugiat leo, sit amet tempus est justo eu augue. Cras eget nibh ac enim bibendum lobortis. Sed ultricies nunc elit, imperdiet consectetur velit scelerisque eu. Aliquam suscipit libero vel nibh porttitor, vel sodales nisi viverra. Duis vitae rutrum metus, vitae accumsan massa. Sed congue, est interdum commodo facilisis, leo libero blandit tellus, a dapibus tortor odio eget ex. Nunc aliquet nulla vel augue pulvinar, vel luctus risus sagittis. Sed non sodales urna. Phasellus quis sapien placerat, ultricies risus ut, hendrerit mi. Donec pretium ligula non arcu posuere porttitor. Pellentesque eleifend mollis ex non eleifend. Nam sed elit mollis mauris laoreet aliquam eget vel elit.''', + ), + ), + ), + ), + codeSnippet: '''Expander( + header: Text('Open to see the scrollable text'), + content: SizedBox( + height: 300, + child: SingleChildScrollView( + child: Text('A LONG TEXT HERE'), + ), + ), )''', ), subtitle(content: const Text('Expander opened programatically')), diff --git a/example/lib/screens/surface/info_bars.dart b/example/lib/screens/surface/info_bars.dart index ac33f8378..ca5dc649f 100644 --- a/example/lib/screens/surface/info_bars.dart +++ b/example/lib/screens/surface/info_bars.dart @@ -75,6 +75,18 @@ class InfoBarPage extends ScrollablePage { .toList(), value: severity, onChanged: (v) => setState(() => severity = v ?? severity), + comboboxColor: () { + switch (severity) { + case InfoBarSeverity.info: + break; + case InfoBarSeverity.warning: + return Colors.warningPrimaryColor; + case InfoBarSeverity.error: + return Colors.errorPrimaryColor; + case InfoBarSeverity.success: + return Colors.successPrimaryColor; + } + }(), ), ), ], diff --git a/example/lib/screens/surface/progress_indicators.dart b/example/lib/screens/surface/progress_indicators.dart index 0f8e99e3b..40ae25a98 100644 --- a/example/lib/screens/surface/progress_indicators.dart +++ b/example/lib/screens/surface/progress_indicators.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:example/widgets/page.dart'; import 'package:fluent_ui/fluent_ui.dart'; @@ -6,18 +8,29 @@ import '../../widgets/card_highlight.dart'; class ProgressIndicatorsPage extends ScrollablePage { @override Widget buildHeader(BuildContext context) { - return const PageHeader(title: Text('ProgressBar and ProgressRing')); + return const PageHeader(title: Text('Progress controls')); } - double determinateValue = 1.0; + double determinateValue = Random().nextDouble() * 100; @override List buildScrollable(BuildContext context) { return [ - const Text( - 'The progress indicators have two different visual representations:\nIndeterminate - shows that a task is ongoing, but blocks user interaction.\nDeterminate - shows how much progress has been made on a known amount of work.', + description( + content: const Text( + 'A progress control provides feedback to the user that a long-running ' + 'operation is underway. It can mean that the user cannot interact with ' + 'the app when the progress indicator is visible, and can also indicate ' + 'how long the wait time might be, depending on the indicator used.', + ), ), subtitle(content: const Text('Indeterminate Progress Indicators')), + description( + content: const Text( + 'The indeterminate state shows that an operation is underway and its ' + 'completion time is unknown.', + ), + ), CardHighlight( child: RepaintBoundary( child: Row(children: const [ @@ -33,6 +46,13 @@ ProgressBar(), ProgressRing(),''', ), subtitle(content: const Text('Determinate Progress Indicators')), + description( + content: const Text( + 'The determinate state shows the percentage completed of a task. ' + 'This should be used during an operation whose duration is known, but ' + 'its progress should not block the user\'s interaction with the app.', + ), + ), CardHighlight( child: Row(children: [ ProgressBar(value: determinateValue), diff --git a/example/lib/screens/surface/tiles.dart b/example/lib/screens/surface/tiles.dart index c1aafcf91..77011f9b0 100644 --- a/example/lib/screens/surface/tiles.dart +++ b/example/lib/screens/surface/tiles.dart @@ -26,7 +26,11 @@ class TilePage extends ScrollablePage { List buildScrollable(BuildContext context) { final theme = FluentTheme.of(context); return [ - const Text('Tiles that are usually used inside a ListView'), + description( + content: const Text( + 'A fluent-styled list tile. Usually used inside a ListView', + ), + ), subtitle(content: const Text('Basic ListView with selectable tiles')), CardHighlight( child: Container( @@ -67,8 +71,7 @@ ListView.builder( onSelectionChange: (v) => setState(() => selectedContact = contact), ); } -) -''', +),''', ), subtitle( content: const Text('ListViewItems with many properties applied'), @@ -121,7 +124,7 @@ ListView.builder( return ListTile.selectable( title: Text(contact), selected: selectedContacts.contains(contact), - selectionMode: ListTileSelectionMode.multiple + selectionMode: ListTileSelectionMode.multiple, onSelectionChange: (selected) { setState(() { if (selected) { @@ -133,8 +136,7 @@ ListView.builder( }, ); } -) -''', +),''', ), subtitle( content: const Text('ListViewItems with images'), @@ -208,8 +210,7 @@ ListView.builder( onSelectionChange: (v) => setState(() => selectedContact = contact), ); } -) -''', +),''', ), ]; } diff --git a/example/lib/widgets/card_highlight.dart b/example/lib/widgets/card_highlight.dart index f8efb3709..f9e37b8a8 100644 --- a/example/lib/widgets/card_highlight.dart +++ b/example/lib/widgets/card_highlight.dart @@ -22,7 +22,8 @@ class CardHighlight extends StatefulWidget { State createState() => _CardHighlightState(); } -class _CardHighlightState extends State { +class _CardHighlightState extends State + with AutomaticKeepAliveClientMixin { bool isOpen = false; bool isCopying = false; @@ -30,6 +31,7 @@ class _CardHighlightState extends State { @override Widget build(BuildContext context) { + super.build(context); final theme = FluentTheme.of(context); return Column(children: [ Card( @@ -111,6 +113,9 @@ class _CardHighlightState extends State { ), ]); } + + @override + bool get wantKeepAlive => true; } const fluentHighlightTheme = { diff --git a/lib/src/controls/surfaces/flyout/content.dart b/lib/src/controls/surfaces/flyout/content.dart index 53bd7c4fe..302d98187 100644 --- a/lib/src/controls/surfaces/flyout/content.dart +++ b/lib/src/controls/surfaces/flyout/content.dart @@ -128,6 +128,8 @@ class FlyoutListTile extends StatelessWidget { @override Widget build(BuildContext context) { assert(debugCheckHasFluentTheme(context)); + final size = ContentSizeInfo.of(context).size; + return HoverButton( key: key, onPressed: onPressed, @@ -145,7 +147,11 @@ class FlyoutListTile extends StatelessWidget { Widget content = Stack(children: [ Container( decoration: BoxDecoration( - color: ButtonThemeData.uncheckedInputColor(theme, states), + color: ButtonThemeData.uncheckedInputColor( + theme, + states, + transparentWhenNone: true, + ), borderRadius: radius, ), padding: const EdgeInsetsDirectional.only( @@ -164,6 +170,7 @@ class FlyoutListTile extends StatelessWidget { ), ), Flexible( + fit: size.isEmpty ? FlexFit.loose : FlexFit.tight, child: Padding( padding: const EdgeInsetsDirectional.only(end: 10.0), child: DefaultTextStyle( diff --git a/lib/src/controls/surfaces/tooltip.dart b/lib/src/controls/surfaces/tooltip.dart index a53728764..60885cf00 100644 --- a/lib/src/controls/surfaces/tooltip.dart +++ b/lib/src/controls/surfaces/tooltip.dart @@ -7,15 +7,17 @@ import 'package:flutter/rendering.dart'; import 'package:fluent_ui/fluent_ui.dart'; -/// A tooltip is a short description that is linked to another -/// control or object. Tooltips help users understand unfamiliar -/// objects that aren't described directly in the UI. They display -/// automatically when the user moves focus to, presses and holds, -/// or hovers the mouse pointer over a control. The tooltip disappears -/// after a few seconds, or when the user moves the finger, pointer -/// or keyboard/gamepad focus. +/// A tooltip is a popup that contains additional information about another +/// control or object. Tooltips display automatically when the user moves focus +/// to, presses and holds, or hovers the pointer over the associated control. +/// The tooltip disappears when the user moves focus from, stops pressing on, +/// or stops hovering the pointer over the associated control (unless the +/// pointer is moving towards the tooltip). /// /// ![Tooltip Preview](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/controls/tool-tip.png) +/// +/// See also: +/// * [Flyout], which creates a popup with interactive content class Tooltip extends StatefulWidget { /// Creates a tooltip. /// diff --git a/lib/src/utils/label.dart b/lib/src/utils/label.dart index 059602f98..ee8bd97f8 100644 --- a/lib/src/utils/label.dart +++ b/lib/src/utils/label.dart @@ -8,21 +8,24 @@ import 'package:flutter/foundation.dart'; /// ![InfoLabel above a TextBox](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/text-box-ex1.png) class InfoLabel extends StatelessWidget { /// Creates an info label. - const InfoLabel({ + InfoLabel({ + Key? key, + this.child, + required String label, + TextStyle? labelStyle, + this.isHeader = true, + }) : label = TextSpan(text: label, style: labelStyle), + super(key: key); + + /// Creates an info label. + const InfoLabel.rich({ Key? key, this.child, required this.label, - this.labelStyle, this.isHeader = true, }) : super(key: key); - /// The text of the label. It'll be styled acorrding to - /// [labelStyle]. If this is empty, a blank space will - /// be rendered. - final String label; - - /// The style of the text. If null, [Typography.body] is used - final TextStyle? labelStyle; + final InlineSpan label; /// The widget to apply the label. final Widget? child; @@ -33,15 +36,14 @@ class InfoLabel extends StatelessWidget { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(StringProperty('label', label)); - properties.add(DiagnosticsProperty('labelStyle', labelStyle)); + properties.add(DiagnosticsProperty('label', label)); } @override Widget build(BuildContext context) { - final labelWidget = Text( + final labelWidget = Text.rich( label, - style: labelStyle ?? FluentTheme.maybeOf(context)?.typography.body, + style: FluentTheme.maybeOf(context)?.typography.body, ); return Flex( direction: isHeader ? Axis.vertical : Axis.horizontal, diff --git a/test/label_test.dart b/test/label_test.dart index 2d66139e6..5ba3388bd 100644 --- a/test/label_test.dart +++ b/test/label_test.dart @@ -10,7 +10,7 @@ void main() { await tester.pumpWidget( wrapApp( - child: const InfoLabel( + child: InfoLabel( label: 'Label text', labelStyle: labelStyle, ), From 6221d0e227f3a19e404b4f28268bbb0e8542b3fb Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Fri, 26 Aug 2022 16:27:43 -0300 Subject: [PATCH 5/7] Tweaks on the app --- README.md | 580 ------------------ example/lib/main.dart | 2 + example/lib/screens/home.dart | 5 +- example/lib/screens/navigation/tree_view.dart | 4 + example/lib/screens/theming/typography.dart | 69 ++- example/lib/widgets/material_equivalents.dart | 302 +++++++++ lib/src/controls/form/pickers/pickers.dart | 15 +- lib/src/controls/navigation/tree_view.dart | 50 +- 8 files changed, 409 insertions(+), 618 deletions(-) create mode 100644 example/lib/widgets/material_equivalents.dart diff --git a/README.md b/README.md index 37a93908b..851d7ceee 100644 --- a/README.md +++ b/README.md @@ -55,20 +55,12 @@ - [Navigation](#navigation) - [Bottom Navigation](#bottom-navigation) - [Widgets](#widgets) - - [Tooltip](#tooltip) - - [Content Dialog](#content-dialog) - - [Expander](#expander) - [Flyout](#flyout) - **TODO** [Teaching tip]() - [Acrylic](#acrylic) - - [InfoBar](#infobar) - **TODO** [Calendar View](#calendar-view) - **TODO** [Calendar Date Picker]() - - [Progress Bar and Progress Ring](#progress-bar-and-progress-ring) - [Scrollbar](#scrollbar) - - [ListTile](#listtile) - - [Info Label](#info-label) - - [CommandBar](#commandbar) - [Mobile Widgets](#mobile-widgets) - [Chip](#chip) - [Pill Button Bar](#pill-button-bar) @@ -165,45 +157,6 @@ FluentTheme( ), ``` -## Icons - -![Icons Preview](https://github.com/microsoft/fluentui-system-icons/raw/master/art/readme-banner.png) - -Inside your app, you use icons to represent an action, such as copying text or navigating to the settings page. This library includes an icon library with it, so you can just call `FluentIcons.[icon_name]` in any `Icon` widget: - -```dart -Icon(FluentIcons.add), -``` - -For a complete reference of current icons, please check the [online demo](https://bdlukaa.github.io/fluent_ui/) and click on "Icons". - -The online demo has a search box and also supports clipboard copy in order to find every icon as fast as possible. - -![Icons in navigation bar](https://docs.microsoft.com/en-us/windows/uwp/design/style/images/icons/inside-icons.png) - -## Colors - -This library also includes the Fluent UI colors with it, so you can just call `Colors.[color_name]`: - -```dart -TextStyle(color: Colors.black), -``` - -Avaiable colors: - -- `Colors.transparent` -- `Colors.white` -- `Colors.black` -- `Colors.grey` -- `Colors.yellow` -- `Colors.orange` -- `Colors.red` -- `Colors.magenta` -- `Colors.purple` -- `Colors.blue` -- `Colors.teal` -- `Colors.green` - ### Accent color Common controls use an accent color to convey state information. [Learn more](https://docs.microsoft.com/en-us/windows/uwp/design/style/color#accent-color). @@ -226,74 +179,6 @@ ThemeData( ) ``` -## Brightness - -You can change the theme brightness to change the color of your app to - -1. `Brightness.light` - - ![Light theme](https://docs.microsoft.com/en-us/windows/uwp/design/style/images/color/light-theme.svg) - -2. `Brightness.dark` - - ![Dark theme](https://docs.microsoft.com/en-us/windows/uwp/design/style/images/color/dark-theme.svg) - -It defaults to the brightness of the device. (`MediaQuery.of(context).platformBrightness`) - -```dart -ThemeData( - brightness: Brightness.light, // or Brightness.dark -), -``` - -## Visual Density - -Density, in the context of a UI, is the vertical and horizontal "compactness" of the components in the UI. It is unitless, since it means different things to different UI components. - -The default for visual densities is zero for both vertical and horizontal densities. It does not affect text sizes, icon sizes, or padding values. - -For example, for buttons, it affects the spacing around the child of the button. For lists, it affects the distance between baselines of entries in the list. For chips, it only affects the vertical size, not the horizontal size. - -```dart -ThemeData( - visualDensity: VisualDensity.adaptivePlatformDensity, -), -``` - -The following widgets make use of visual density: - -- Chip -- PillButtonBar -- Snackbar - -## Typography - -To set a typography, you can use the `ThemeData` class combined with the `Typography` class: - -```dart -ThemeData( - typography: Typography( - caption: TextStyle( - fontSize: 12, - color: Colors.black, - fontWeight: FontWeight.normal, - ), - ), -) -``` - -### Font - -You should use one font throughout your app's UI, and we recommend sticking with the default font for Windows apps, **Segoe UI Variable**. It's designed to maintain optimal legibility across sizes and pixel densities and offers a clean, light, and open aesthetic that complements the content of the system. [Learn more](https://docs.microsoft.com/en-us/windows/apps/design/style/typography#font) - -![Font Segoe UI Showcase](https://docs.microsoft.com/en-us/windows/apps/design/style/images/type/segoe-sample.svg) - -### Type ramp - -The Windows type ramp establishes crucial relationships between the type styles on a page, helping users read content easily. All sizes are in effective pixels. [Learn more](https://docs.microsoft.com/en-us/windows/apps/design/style/typography#type-ramp) - -![Windows Type Ramp](https://docs.microsoft.com/en-us/windows/apps/design/style/images/type/text-block-type-ramp.svg) - ## Reveal Focus Reveal Focus is a lighting effect for [10-foot experiences](https://docs.microsoft.com/en-us/windows/uwp/design/devices/designing-for-tv), such as Xbox One and television screens. It animates the border of focusable elements, such as buttons, when the user moves gamepad or keyboard focus to them. It's turned off by default, but it's simple to enable. [Learn more](https://docs.microsoft.com/en-us/windows/uwp/design/style/reveal-focus) @@ -362,471 +247,6 @@ FocusTheme( ), ``` -# Navigation - -The default Flutter Navigation is available on the `FluentApp` widget, that means you can simply call `Navigator.push` and `Navigator.pop` to navigate between routes. See [navigate to a new screen and back](https://flutter.dev/docs/cookbook/navigation/navigation-basics) - -## Bottom Navigation - -The bottom navigation displays icons and optional text at the bottom of the screen for switching between different primary destinations in an app. This is commomly used on small screens. [Learn more](https://developer.microsoft.com/pt-br/fluentui#/controls/android/bottomnavigation) - -Here's an example of how to create a bottom navigation: - -```dart -int index = 0; - -ScaffoldPage( - content: NavigationBody(index: index, children: [ - Container(), - Container(), - Container(), - ]), - bottomBar: BottomNavigation( - index: index, - onChanged: (i) => setState(() => index = i), - items: [ - BottomNavigationItem( - icon: Icon(Icons.two_k), - selectedIcon: Icon(Icons.two_k_plus), - title: Text('Both'), - ), - BottomNavigationItem( - icon: Icon(Icons.phone_android_outlined), - selectedIcon: Icon(Icons.phone_android), - title: Text('Android'), - ), - BottomNavigationItem( - icon: Icon(Icons.phone_iphone_outlined), - selectedIcon: Icon(Icons.phone_iphone), - title: Text('iOS'), - ), - ], - ) -) - -``` - -# Inputs - -Inputs are widgets that reacts to user interection. On most of the inputs you can set `onPressed` or `onChanged` to `null` to disable it. - - -## Rating Bar - -> The property `starSpacing` was not implemented yet - -The rating control allows users to view and set ratings that reflect degrees of satisfaction with content and services. [Learn more](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/rating) - -### Example - -```dart -double rating = 0.0; - -RatingBar( - rating: rating, - onChanged: (v) => setState(() => rating = v), -) -``` - -You can set `amount` to change the amount of stars. The `rating` must be less than the stars and more than 0. You can also change the `icon`, its size and color. You can make the bar read only by setting `onChanged` to `null`. - -![Rating Bar](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/rating_rs2_doc_ratings_intro.png) - -# Widgets - -## Flyout - -A flyout is a light dismiss container that can show arbitrary UI as its content. Flyouts can contain other flyouts or context menus to create a nested experience. - -![Flyout Opened Above Button 3](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/flyout-smoke.png) - -```dart -final flyoutController = FlyoutController(); - -Flyout( - controller: flyoutController, - content: const FlyoutContent( - constraints: BoxConstraints(maxWidth: 100), - child: Text('The Flyout for Button 3 has LightDismissOverlayMode enabled'), - ), - child: Button( - child: Text('Button 3'), - onPressed: flyoutController.open, - ), -); - -@override -void dispose() { - // Dispose the controller to free up resources - flyoutController.dispose(); - super.dispose(); -} -``` - -## Scrollbar - -A scrollbar thumb indicates which portion of a [ScrollView] is actually visible. [Learn more](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/scroll-controls) - -Depending on the situation, the scrollbar uses two different visualizations, shown in the following illustration: the panning indicator (left) and the traditional scrollbar (right). - -> Note that the arrows aren't visible. See [#80370](https://github.com/flutter/flutter/issues/80370) and [#14](https://github.com/bdlukaa/fluent_ui/issues/14) issues for more info. - -![Scrollbar Panning Indicator](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/scrollbar-panning.png) -![Traditional Scrollbar](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/scrollbar-traditional.png) - -> When the scrollbar is visible it is overlaid as 16px on top of the content inside your ScrollView. In order to ensure good UX design you will want to ensure that no interactive content is obscured by this overlay. Additionally if you would prefer not to have UX overlap, leave 16px of padding on the edge of the viewport to allow for the scrollbar. - -Here's an example of how to add a scrollbar to a ScrollView: - -```dart -final _controller = ScrollController(); - -Scrollbar( - controller: _controller, - child: ListView.builder( - controller: _controller, - /// You can add a padding to the view to avoid having the scrollbar over the UI elements - padding: EdgeInsets.only(right: 16.0), - itemCount: 100, - itemBuilder: (context, index) { - return ListTile(title: Text('$index')); - } - ), -) -``` - -Which produces the following: - -![Scrollbar Preview](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/conscious-scroll.gif) - -You can change the `isAlwaysVisible` property to either enable or disable the fade effect. It's disabled by default. - -## ListTile - -A fluent-styled list tile. Usually used inside a `ListView`. [Learn more](https://docs.microsoft.com/en-us/windows/apps/design/controls/item-templates-listview) - -Here's an example of how to use a list tile inside a of `ListView`: - -```dart -String selectedContact = ''; - -const contacts = ['Kendall', 'Collins', ...]; - -ListView.builder( - itemCount: contacts.length, - itemBuilder: (context, index) { - final contact = contacts[index]; - return ListTile.selectable( - leading: CircleAvatar(), - title: Text(contact), - selected: selectedContact == contact, - onSelectionChange: (v) => setState(() => selectedContact = contact), - ); - } -) -``` - -The code above produces the following: - -![A selected ListTile in a ListView](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/listview-grouped-example-resized-final.png) - -## Info Label - -You can use an `InfoLabel` to tell the user the purpose of something. - -Here's an example of how to add an info header to a combo box: - -```dart -InfoLabel( - label: 'Colors', - child: ComboBox(...), -), -``` - -The code above produces the following: - -![InfoHeader Preview](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/combo-box-no-selection.png) - -Some widgets, such as `ComboBox` and `TextBox`, already come with a `header` property, so you can use them easily with them: - -```dart -ComboBox( - header: 'Control header', - ... -) -``` - -This will produce the same as the image above. - -### Scrollable tree view - -Vertical scrolling can be enabled for a tree view by setting the `shrinkWrap` property to false. -If you have many items, consider setting `itemExtent`, `cacheExtent`, and/or `usePrototypeItem` -for much better performance. - -### Lazily load nodes - -Load nodes as required by the user - -```dart -late List items; - -@override -void initState() { - super.initState(); - items = [ - TreeViewItem( - content: const Text('Parent node'), - children: [], // REQUIRED. An initial list of children must be provided. It must be mutable - onInvoked: (item) async { - // If the node is already populated, return - if (item.children.isNotEmpty) return; - setState(() => item.loading = true); - // Fetch more nodes from an async source, such as an API or device storage - final List nodes = await fetchNodes(); - setState(() { - item.loading = false; - item.children.addAll(nodes); - }); - } - ) - ]; -} - -TreeView( - items: items, -); -``` - -## CommandBar - -A `CommandBar` control provides quick access to common tasks. This could be application-level or page-level commands. [Learn More](https://docs.microsoft.com/en-us/windows/apps/design/controls/command-bar) - -![CommandBar Simple](https://docs.microsoft.com/en-us/windows/apps/design/controls/images/controls-appbar-icons.png) - -The `CommandBar` is composed of a number of `CommandBarItem` objects, which could be `CommandBarButton`, a `CommandBarSeparator`, or any custom object (e.g., a "split button" object). Sub-class `CommandBarItem` to create your own custom items. - -Each `CommandBarItem` widget knows how to render itself in three different modes: - -- `CommandBarItemDisplayMode.inPrimary`: Displayed horizontally in primary area -- `CommandBarItemDisplayMode.inPrimaryCompact`: More compact horizontal display (e.g., only the icon is displayed for `CommandBarButton`) -- `CommandBarItemDisplayMode.inSecondary`: Displayed within flyout menu `ListView` - -The "primary area" of the command bar displays items horizontally. The "secondary area" of the command bar is a flyout menu accessed via an "overflow widget" (by default, a "more" button). You can specify items that should be displayed for each area. The overflow widget will only be displayed if there are items in the secondary area (including any items that dynamically overflowed into the secondary area, if dynamic overflow is enabled). - -Whether or not the "compact" mode is selected for items displayed in the primary area is determined by an optional width breakpoint. If set, if the width of the widget is less than the breakpoint, it will render each primary `CommandBarItem` using the compact mode. - -Different behaviors can be selected when the width of the `CommandBarItem` widgets exceeds the constraints, as determined by the specified `CommandBarOverflowBehavior`, including dynamic overflow (putting primary items into the secondary area on overflow), wrapping, clipping, scrolling, and no wrapping (will overflow). - -The horizontal and vertical alignment can also be customized via the `mainAxisAlignment` and `crossAxisAlignment` properties. The main axis alignment respects current directionality. - -A `CommandBarCard` can be used to create a raised card around a `CommandBar`. While this is not officially part of the Fluent design language, the concept is commonly used in the Office desktop apps for the app-level command bar. - -Here is an example of a right-aligned command bar that has additional items in the secondary area: - -```dart -CommandBar( - mainAxisAlignment: MainAxisAlignment.end, - overflowBehavior: CommandBarOverflowBehavior.dynamicOverflow, - compactBreakpointWidth: 768, - primaryItems: [ - CommandBarButton( - icon: const Icon(FluentIcons.add), - label: const Text('Add'), - onPressed: () {}, - ), - CommandBarButton( - icon: const Icon(FluentIcons.edit), - label: const Text('Edit'), - onPressed: () {}, - ), - CommandBarButton( - icon: const Icon(FluentIcons.delete), - label: const Text('Edit'), - onPressed: () {}, - ), - ], - secondaryItems: [ - CommandBarButton( - icon: const Icon(FluentIcons.archive), - label: const Text('Archive'), - onPressed: () {}, - ), - CommandBarButton( - icon: const Icon(FluentIcons.move), - label: const Text('Move'), - onPressed: () {}, - ), - ], -), -``` - -To put a tooltip on any other kind of `CommandBarItem` (or otherwise wrap it in another widget), use `CommandBarBuilderItem`: - -```dart -CommandBarBuilderItem( - builder: (context, mode, w) => Tooltip( - message: "Create something new!", - child: w, - ), - wrappedItem: CommandBarButton( - icon: const Icon(FluentIcons.add), - label: const Text('Add'), - onPressed: () {}, - ), -), -``` - -More complex examples, including command bars with items that align to each side of a carded bar, are in the example app. - -# Mobile Widgets - -Widgets with focus on mobile. Based on the official documentation and source code for [iOS](https://developer.microsoft.com/pt-br/fluentui#/controls/ios) and [Android](https://developer.microsoft.com/pt-br/fluentui#/controls/android). Most of the widgets above can adapt to small screens, and will fit on all your devices. - -## Bottom Sheet - -Bottom Sheet is used to display a modal list of menu items. They slide up over the main app content as a result of a user triggered action. [Learn more](https://developer.microsoft.com/pt-br/fluentui#/controls/android/bottomsheet) - -Here's an example of how to display a bottom sheet: - -```dart -showBottomSheet( - context: context, - builder: (context) { - return BottomSheet( - // header: ..., - description: Text('Description or Details here'), - children: [ - ..., - // Usually a `ListTile` or `TappableListTile` - ], - ); - }, -), -``` - -To close it, just call `Navigator.of(context).pop()` - -![Bottom Sheet Showcase](https://static2.sharepointonline.com/files/fabric/fabric-website/images/controls/android/updated/img_bottomsheet_01_light.png?text=LightMode) - -## Chip - -Chips are compact representations of entities (most commonly, people) that can be clicked, deleted, or dragged easily. - -Here's an example of how to create a chip: - -```dart -Chip( - image: CircleAvatar(size: 12.0), - text: Text('Chip'), -), -Chip.selected( - image: FlutterLogo(size: 14.0), - text: Text('Chip'), -) -``` - -![Light Chips](https://user-images.githubusercontent.com/45696119/119724339-f9a00700-be44-11eb-940b-1966eefe3798.png) - -![Dark Chips](https://user-images.githubusercontent.com/45696119/119724337-f9077080-be44-11eb-9b73-e1dc4ffbeefd.png) - -## Pill Button Bar - -A Pill Button Bar is a horizontal scrollable list of pill-shaped text buttons in which only one button can be selected at a given time. - -Here's an example of how to create a pill button bar: - -```dart -int index = 0; - -PillButtonBar( - selected: index, - onChanged: (i) => setState(() => index = i), - items: [ - PillButtonBarItem(text: Text('All')), - PillButtonBarItem(text: Text('Mail')), - PillButtonBarItem(text: Text('Peopl')), - PillButtonBarItem(text: Text('Events')), - ] -) -``` - -![Light PillButtonBar](https://static2.sharepointonline.com/files/fabric/fabric-website/images/controls/ios/updated/img_pillbar_01_light.png?text=LightMode) - -![Dark PillButtonBar](https://static2.sharepointonline.com/files/fabric/fabric-website/images/controls/ios/updated/img_pillbar_01_dark.png?text=DarkMode) - -## Snackbar - -Snackbars provide a brief message about an operation at the bottom of the screen. They can contain a custom action or view or use a style geared towards making special announcements to your users. - -Here's an example of how to display a snackbar at the bottom of the screen: - -```dart -showSnackbar( - context, - Snackbar( - content: Text('A new update is available!'), - ), -); -``` - -![Snackbar Example](https://static2.sharepointonline.com/files/fabric/fabric-website/images/controls/android/updated/img_snackbar_01_standard_dark.png?text=DarkMode) - ---- - -# Layout Widgets - -Widgets that help to layout other widgets. - -## DynamicOverflow - -`DynamicOverflow` widget is similar to the `Wrap` widget, but only lays out children widgets in a single run, and if there is not room to display them all, it will hide widgets that don't fit, and display the "overflow widget" at the end. Optionally, the "overflow widget" can be displayed all the time. Displaying the overflow widget will take precedence over any children widgets. - -This is used to implement the dynamic overflow mode for `CommandBar`, but could be useful on its own. It supports both horizontal and vertical layout modes, and various main axis and cross axis alignments. - -# Equivalents with the material library - -The list of equivalents between this library and `flutter/material.dart` - -| Material | Fluent | -| ------------------------- | ---------------- | -| TextButton | Button | -| IconButton | IconButton | -| Checkbox | Checkbox | -| RadioButton | RadioButton | -| - | RatingBar | -| - | SplitButton | -| - | ToggleButton | -| Switch | ToggleSwitch | -| TextField | TextBox | -| TextFormField | TextFormBox | -| DropdownButton | ComboBox | -| PopupMenuButton | DropDownButton | -| - | AutoSuggestBox | -| AlertDialog | ContentDialog | -| MaterialBanner | InfoBar | -| Tooltip | Tooltip | -| - | Flyout | -| Drawer | NavigationPane | -| BottomNavigation | BottomNavigation | -| Divider | Divider | -| VerticalDivider | Divider | -| Material | Acrylic | -| ListTile | ListTile | -| CheckboxListTile | CheckboxListTile | -| SwitchListTile | SwitchListTile | -| LinearProgressIndicator | ProgressBar | -| CircularProgressIndicator | ProgressRing | -| \_DatePickerDialog | DatePicker | -| \_TimePickerDialog | TimePicker | -| Scaffold | ScaffoldPage | -| AppBar | NavigationAppBar | -| Drawer | NavigationView | -| Chip | Chip | -| Snackbar | Snackbar | -| - | PillButtonBar | -| ExpansionPanel | Expander | - ## Localization FluentUI widgets currently supports out-of-the-box an wide number of languages, including: diff --git a/example/lib/main.dart b/example/lib/main.dart index e1cbd86f8..7cf5e94cf 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -298,6 +298,8 @@ class _MyHomePageState extends State with WindowListener { const IconsPage().toPage(), // others Settings(), + + // TODO: mobile widgets, Scrollbar, BottomNavigationBar, RatingBar ]; @override diff --git a/example/lib/screens/home.dart b/example/lib/screens/home.dart index da1d0ac74..4446f6d19 100644 --- a/example/lib/screens/home.dart +++ b/example/lib/screens/home.dart @@ -3,6 +3,7 @@ import 'package:url_launcher/link.dart'; import '../models/sponsor.dart'; import '../widgets/changelog.dart'; +import '../widgets/material_equivalents.dart'; import '../widgets/page.dart'; import '../widgets/sponsor.dart'; @@ -209,9 +210,11 @@ class HomePage extends ScrollablePage { ), const Text('Become a Sponsor!'), ]), - ) + ), ], ), + subtitle(content: const Text('Equivalents with the material library')), + const MaterialEquivalents(), ]; } } diff --git a/example/lib/screens/navigation/tree_view.dart b/example/lib/screens/navigation/tree_view.dart index 4f511fa9a..9dca768ee 100644 --- a/example/lib/screens/navigation/tree_view.dart +++ b/example/lib/screens/navigation/tree_view.dart @@ -250,6 +250,10 @@ TreeView( ), ]); }); + setState(() { + item.expanded = true; + item.updateSelected(); + }); }, ), ]; diff --git a/example/lib/screens/theming/typography.dart b/example/lib/screens/theming/typography.dart index 9f21ee8ef..3cf977465 100644 --- a/example/lib/screens/theming/typography.dart +++ b/example/lib/screens/theming/typography.dart @@ -85,41 +85,42 @@ class _TypographyPageState extends State { const Divider( style: DividerThemeData(horizontalMargin: EdgeInsets.zero), ), + const SizedBox(height: 4.0), + const Text( + 'The Windows type ramp establishes crucial relationships ' + 'between the type styles on a page, helping users read content ' + 'easily.', + ), Expanded( - child: ListView( - children: [ - Text('Display', - style: - typography.display?.apply(fontSizeFactor: scale)), - spacer, - Text('Title Large', - style: typography.titleLarge - ?.apply(fontSizeFactor: scale)), - spacer, - Text('Title', - style: typography.title?.apply(fontSizeFactor: scale)), - spacer, - Text('Subtitle', - style: - typography.subtitle?.apply(fontSizeFactor: scale)), - spacer, - Text('Body Large', - style: - typography.bodyLarge?.apply(fontSizeFactor: scale)), - spacer, - Text('Body Strong', - style: typography.bodyStrong - ?.apply(fontSizeFactor: scale)), - spacer, - Text('Body', - style: typography.body?.apply(fontSizeFactor: scale)), - spacer, - Text('Caption', - style: - typography.caption?.apply(fontSizeFactor: scale)), - spacer, - ], - ), + child: ListView(children: [ + Text('Display', + style: typography.display?.apply(fontSizeFactor: scale)), + spacer, + Text('Title Large', + style: + typography.titleLarge?.apply(fontSizeFactor: scale)), + spacer, + Text('Title', + style: typography.title?.apply(fontSizeFactor: scale)), + spacer, + Text('Subtitle', + style: typography.subtitle?.apply(fontSizeFactor: scale)), + spacer, + Text('Body Large', + style: + typography.bodyLarge?.apply(fontSizeFactor: scale)), + spacer, + Text('Body Strong', + style: + typography.bodyStrong?.apply(fontSizeFactor: scale)), + spacer, + Text('Body', + style: typography.body?.apply(fontSizeFactor: scale)), + spacer, + Text('Caption', + style: typography.caption?.apply(fontSizeFactor: scale)), + spacer, + ]), ), ], ), diff --git a/example/lib/widgets/material_equivalents.dart b/example/lib/widgets/material_equivalents.dart new file mode 100644 index 000000000..eb68ffb8d --- /dev/null +++ b/example/lib/widgets/material_equivalents.dart @@ -0,0 +1,302 @@ +import 'dart:math'; + +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/material.dart' as m; + +class MaterialEquivalents extends StatefulWidget { + const MaterialEquivalents({Key? key}) : super(key: key); + + @override + State createState() => _MaterialEquivalentsState(); +} + +class _MaterialEquivalentsState extends State { + bool comboboxChecked = true; + bool radioChecked = true; + bool switchChecked = true; + + final List comboboxItems = [ + 'Item 1', + 'Item 2', + ]; + String? comboboxItem; + String dropdownItem = 'Item 1'; + final popupKey = GlobalKey(); + + double sliderValue = Random().nextDouble() * 100; + + final fieldController = TextEditingController(); + DateTime time = DateTime.now(); + + @override + void dispose() { + fieldController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + List> children = [ + [ + const Text('TextButton'), + Button( + child: const Text('Content'), + onPressed: () {}, + ), + m.OutlinedButton( + child: const Text('Content'), + onPressed: () {}, + ), + ], + [ + const Text('TextButton'), + TextButton( + child: const Text('Content'), + onPressed: () {}, + ), + m.TextButton( + child: const Text('Content'), + onPressed: () {}, + ), + ], + [ + const Text('FilledButton'), + FilledButton( + child: const Text('Content'), + onPressed: () {}, + ), + m.ElevatedButton( + child: const Text('Content'), + onPressed: () {}, + ), + ], + [ + const Text('IconButton'), + IconButton( + icon: const Icon(FluentIcons.graph_symbol), + onPressed: () {}, + ), + m.IconButton( + icon: const Icon(FluentIcons.graph_symbol), + onPressed: () {}, + ), + ], + [ + const Text('Checkbox'), + Checkbox( + checked: comboboxChecked, + onChanged: (v) => + setState(() => comboboxChecked = v ?? comboboxChecked), + ), + m.Checkbox( + value: comboboxChecked, + onChanged: (v) => + setState(() => comboboxChecked = v ?? comboboxChecked), + ), + ], + [ + const Text('RadioButton'), + RadioButton( + checked: radioChecked, + onChanged: (v) => setState(() => radioChecked = v), + ), + m.Radio( + groupValue: true, + value: radioChecked, + onChanged: (v) => setState(() => radioChecked = v ?? radioChecked), + ), + ], + [ + const Text('ToggleSwitch'), + ToggleSwitch( + checked: switchChecked, + onChanged: (v) => setState(() => switchChecked = v), + ), + m.Switch( + value: switchChecked, + onChanged: (v) => setState(() => switchChecked = v), + ), + ], + [ + const Text('Slider'), + Slider( + value: sliderValue, + max: 100, + onChanged: (v) => setState(() => sliderValue = v), + ), + m.Slider( + value: sliderValue, + max: 100, + onChanged: (v) => setState(() => sliderValue = v), + ), + ], + [ + const Text('ProgressRing'), + const RepaintBoundary(child: ProgressRing()), + const RepaintBoundary(child: m.CircularProgressIndicator()), + ], + [ + const Text('ProgressBar'), + const RepaintBoundary(child: ProgressBar()), + const RepaintBoundary(child: m.LinearProgressIndicator()), + ], + [ + const Text('ComboBox'), + ComboBox( + items: comboboxItems + .map((e) => ComboboxItem(child: Text(e), value: e)) + .toList(), + value: comboboxItem, + onChanged: (value) => setState(() => comboboxItem = value), + ), + m.DropdownButton( + items: comboboxItems + .map((e) => m.DropdownMenuItem(child: Text(e), value: e)) + .toList(), + value: comboboxItem, + onChanged: (value) => setState(() => comboboxItem = value), + ), + ], + [ + const Text('DropDownButton'), + DropDownButton( + items: comboboxItems + .map( + (e) => MenuFlyoutItem( + text: Text(e), + onPressed: () => setState(() => dropdownItem = e), + ), + ) + .toList(), + title: Text(dropdownItem), + ), + m.PopupMenuButton( + key: popupKey, + itemBuilder: (context) { + return comboboxItems + .map( + (e) => m.PopupMenuItem( + child: Text(e), + value: e, + ), + ) + .toList(); + }, + onSelected: (e) => setState(() => dropdownItem = e), + initialValue: dropdownItem, + position: m.PopupMenuPosition.under, + child: TextButton( + child: Text(dropdownItem), + onPressed: () { + popupKey.currentState?.showButtonMenu(); + }, + ), + ), + ], + [ + const Text('TextBox'), + TextBox(controller: fieldController), + m.TextField(controller: fieldController), + ], + [ + const Text('TimePicker'), + TimePicker( + selected: time, + onChanged: (value) => setState(() => time), + ), + m.TextButton( + child: const Text('Show Picker'), + onPressed: () async { + final newTime = await m.showTimePicker( + context: context, + initialTime: m.TimeOfDay( + hour: time.hour, + minute: time.minute, + ), + ); + if (newTime != null) { + time = DateTime( + time.year, + time.month, + time.day, + newTime.hour, + newTime.minute, + time.second, + ); + } + }, + ), + ], + [ + const Text('DatePicker'), + DatePicker( + selected: time, + onChanged: (value) => setState(() => time), + ), + m.TextButton( + child: const Text('Show Picker'), + onPressed: () async { + final newTime = await m.showDatePicker( + context: context, + initialDate: time, + firstDate: DateTime(time.year - 100), + lastDate: DateTime(time.year + 100), + ); + if (newTime != null) { + setState(() => time = newTime); + } + }, + ), + ], + [ + const Text('ListTile'), + ListTile( + leading: const Icon(FluentIcons.graph_symbol), + title: const Text('Content'), + onPressed: () {}, + ), + m.ListTile( + leading: const Icon(FluentIcons.graph_symbol), + title: const Text('Content'), + onTap: () {}, + ), + ], + [ + const Text('Tooltip'), + const Tooltip( + message: 'A fluent-styled tooltip', + child: Text('Hover'), + ), + const m.Tooltip( + message: 'A material-styled tooltip', + child: Text('Hover'), + ), + ], + ]; + + Widget buildColumn(int index) { + return Column( + children: children + .map( + (children) => Container( + constraints: const BoxConstraints(minHeight: 50.0), + alignment: Alignment.center, + child: children[index], + ), + ) + .toList(), + ); + } + + return m.Material( + type: m.MaterialType.transparency, + child: Row(children: [ + Expanded(child: buildColumn(0)), + const m.VerticalDivider(), + Expanded(child: buildColumn(1)), + const m.VerticalDivider(), + Expanded(child: buildColumn(2)), + ]), + ); + } +} diff --git a/lib/src/controls/form/pickers/pickers.dart b/lib/src/controls/form/pickers/pickers.dart index c0f25d4bc..59ae6c01e 100644 --- a/lib/src/controls/form/pickers/pickers.dart +++ b/lib/src/controls/form/pickers/pickers.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:fluent_ui/fluent_ui.dart'; /// The padding used on the content of [DatePicker] and [TimePicker] @@ -301,12 +303,21 @@ class _PickerState extends State { final theme = FluentTheme.of(context); + const minWidth = 260.0; + final width = max(box.size.width, minWidth); + final x = () { + if (box.size.width > minWidth) return childOffset.dx; + + // if the box width is less than [minWidth], center the popup + return childOffset.dx - (width / 4); + }(); + final view = Stack(children: [ Positioned( - left: childOffset.dx, + left: x, top: y, height: widget.pickerHeight, - width: box.size.width, + width: width, child: FadeTransition( opacity: primary, child: Container( diff --git a/lib/src/controls/navigation/tree_view.dart b/lib/src/controls/navigation/tree_view.dart index 86ac14fcb..51f366f87 100644 --- a/lib/src/controls/navigation/tree_view.dart +++ b/lib/src/controls/navigation/tree_view.dart @@ -1,6 +1,7 @@ -import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/foundation.dart'; +import 'package:fluent_ui/fluent_ui.dart'; + const double _whiteSpace = 28.0; /// Default loading indicator used by [TreeView] @@ -255,6 +256,53 @@ class TreeViewItem with Diagnosticable { ..add(FlagProperty('loading', value: loading, defaultValue: false, ifFalse: 'not loading')); } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is TreeViewItem && + other.key == key && + other.leading == leading && + other.content == content && + other.value == value && + listEquals(other.children, children) && + other.collapsable == collapsable && + other._anyExpandableSiblings == _anyExpandableSiblings && + other.expanded == expanded && + other.selected == selected && + other.onInvoked == onInvoked && + other.backgroundColor == backgroundColor && + other._visible == _visible && + other.autofocus == autofocus && + other.focusNode == focusNode && + other.semanticLabel == semanticLabel && + other.loading == loading && + other.loadingWidget == loadingWidget && + other.lazy == lazy; + } + + @override + int get hashCode { + return key.hashCode ^ + leading.hashCode ^ + content.hashCode ^ + value.hashCode ^ + children.hashCode ^ + collapsable.hashCode ^ + _anyExpandableSiblings.hashCode ^ + expanded.hashCode ^ + selected.hashCode ^ + onInvoked.hashCode ^ + backgroundColor.hashCode ^ + _visible.hashCode ^ + autofocus.hashCode ^ + focusNode.hashCode ^ + semanticLabel.hashCode ^ + loading.hashCode ^ + loadingWidget.hashCode ^ + lazy.hashCode; + } } extension TreeViewItemCollection on List { From 2cdf5423a9a4afd99da9c2e02bb82dbf1bdccd62 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Fri, 26 Aug 2022 18:39:07 -0300 Subject: [PATCH 6/7] Add reveal focus and clean up the README --- README.md | 128 +----------------- example/lib/main.dart | 6 + example/lib/screens/inputs/slider.dart | 1 - example/lib/screens/theming/reveal_focus.dart | 93 +++++++++++++ example/lib/widgets/page.dart | 48 +++---- 5 files changed, 127 insertions(+), 149 deletions(-) create mode 100644 example/lib/screens/theming/reveal_focus.dart diff --git a/README.md b/README.md index 851d7ceee..018e1de7c 100644 --- a/README.md +++ b/README.md @@ -42,32 +42,7 @@ - [Motivation](#motivation) - [Installation](#installation) - [Badge](#badge) -- [Style](#style) - - [Icons](#icons) - - [Colors](#colors) - - [Accent color](#accent-color) - - [Brightness](#brightness) - - [Visual Density](#visual-density) - - [Typograpy](#typography) - - [Font](#font) - - [Type ramp](#type-ramp) - - [Reveal Focus](#reveal-focus) -- [Navigation](#navigation) - - [Bottom Navigation](#bottom-navigation) -- [Widgets](#widgets) - - [Flyout](#flyout) - - **TODO** [Teaching tip]() - - [Acrylic](#acrylic) - - **TODO** [Calendar View](#calendar-view) - - **TODO** [Calendar Date Picker]() - - [Scrollbar](#scrollbar) -- [Mobile Widgets](#mobile-widgets) - - [Chip](#chip) - - [Pill Button Bar](#pill-button-bar) - - [Snackbar](#snackbar) -- [Layout Widgets](#layout-widgets) - - [DynamicOverflow](#dynamicoverflow) -- [Equivalents with the material library](#equivalents-with-the-material-library) +- [Accent color](#accent-color) - [Localization](#Localization) - [Contribution](#contribution) - [Contributing new localizations](#contributing-new-localizations) @@ -80,9 +55,9 @@ See [this](https://github.com/flutter/flutter/issues/46481) for more info on the See also: -- [Material UI for Flutter](https://flutter.dev/docs/development/ui/widgets/material) -- [Cupertino UI for Flutter](https://flutter.dev/docs/development/ui/widgets/cupertino) -- [MacOS UI for Flutter](https://github.com/GroovinChip/macos_ui) + * [Material UI for Flutter](https://flutter.dev/docs/development/ui/widgets/material) + * [Cupertino UI for Flutter](https://flutter.dev/docs/development/ui/widgets/cupertino) + * [MacOS UI for Flutter](https://github.com/GroovinChip/macos_ui) ## Installation @@ -129,33 +104,6 @@ Add the following code to your `README.md` or to your website: --- -# Style - -[Learn more about Fluent Style](https://docs.microsoft.com/en-us/windows/uwp/design/style/) - -You can use the `FluentTheme` widget to, well... theme your widgets. You can style your widgets in two ways: - -1. Using the `FluentApp` widget - -```dart -FluentApp( - title: 'MyApp', - theme: ThemeData( - ... - ), -) -``` - -2. Using the `FluentTheme` widget - -```dart -FluentTheme( - theme: ThemeData( - ... - ), - child: ..., -), -``` ### Accent color @@ -179,74 +127,6 @@ ThemeData( ) ``` -## Reveal Focus - -Reveal Focus is a lighting effect for [10-foot experiences](https://docs.microsoft.com/en-us/windows/uwp/design/devices/designing-for-tv), such as Xbox One and television screens. It animates the border of focusable elements, such as buttons, when the user moves gamepad or keyboard focus to them. It's turned off by default, but it's simple to enable. [Learn more](https://docs.microsoft.com/en-us/windows/uwp/design/style/reveal-focus) - -Reveal Focus calls attention to focused elements by adding an animated glow around the element's border: - -![Reveal Focus Preview](https://docs.microsoft.com/en-us/windows/uwp/design/style/images/traveling-focus-fullscreen-light-rf.gif) - -This is especially helpful in 10-foot scenarios where the user might not be paying full attention to the entire TV screen. - -### Enabling it - -Reveal Focus is off by default. To enable it, change the `focusTheme` in your app `ThemeData`: - -```dart -theme: ThemeData( - focusTheme: FocusThemeData( - glowFactor: 4.0, - ), -), -``` - -To enable it in a 10 foot screen, use the method `is10footScreen`: - -```dart -import 'dart:ui' as ui; - -theme: ThemeData( - focusTheme: FocusThemeData( - glowFactor: is10footScreen(ui.window.physicalSize.width) ? 2.0 : 0.0, - ), -), -``` - -Go to the [example](/example) project to a full example - -### Why isn't Reveal Focus on by default? - -As you can see, it's fairly easy to turn on Reveal Focus when the app detects it's running on 10 foot screen. So, why doesn't the system just turn it on for you? Because Reveal Focus increases the size of the focus visual, which might cause issues with your UI layout. In some cases, you'll want to customize the Reveal Focus effect to optimize it for your app. - -### Customizing Reveal Focus - -You can customize the focus border, border radius and glow color: - -```dart -focusTheme: FocusStyle( - borderRadius: BorderRadius.zero, - glowColor: theme.accentColor?.withOpacity(0.2), - glowFactor: 0.0, - border: BorderSide( - width: 2.0, - color: theme.inactiveColor ?? Colors.transparent, - ), -), -``` - -To customize it to a single widget, wrap the widget in a `FocusTheme` widget, and change the options you want: - -```dart -FocusTheme( - data: FocusThemeData(...), - child: Button( - text: Text('Custom Focus Button'), - onPressed: () {}, - ) -), -``` - ## Localization FluentUI widgets currently supports out-of-the-box an wide number of languages, including: diff --git a/example/lib/main.dart b/example/lib/main.dart index 7cf5e94cf..b34e75fab 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -33,6 +33,7 @@ import 'screens/surface/progress_indicators.dart'; import 'screens/surface/tooltip.dart'; import 'screens/theming/colors.dart'; import 'screens/theming/icons.dart'; +import 'screens/theming/reveal_focus.dart'; import 'screens/theming/typography.dart'; import 'theme.dart'; @@ -250,6 +251,10 @@ class _MyHomePageState extends State with WindowListener { icon: const Icon(FluentIcons.icon_sets_flag), title: const Text('Icons'), ), + PaneItem( + icon: const Icon(FluentIcons.focus), + title: const Text('Reveal Focus'), + ), ]; final List footerItems = [ PaneItemSeparator(), @@ -296,6 +301,7 @@ class _MyHomePageState extends State with WindowListener { ColorsPage(), const TypographyPage().toPage(), const IconsPage().toPage(), + RevealFocusPage(), // others Settings(), diff --git a/example/lib/screens/inputs/slider.dart b/example/lib/screens/inputs/slider.dart index 9fabc9507..8a39b9f0c 100644 --- a/example/lib/screens/inputs/slider.dart +++ b/example/lib/screens/inputs/slider.dart @@ -88,7 +88,6 @@ Slider( ), ''', ), - // TODO: slider label ]; } } diff --git a/example/lib/screens/theming/reveal_focus.dart b/example/lib/screens/theming/reveal_focus.dart new file mode 100644 index 000000000..412881154 --- /dev/null +++ b/example/lib/screens/theming/reveal_focus.dart @@ -0,0 +1,93 @@ +import 'dart:math'; + +import 'package:example/widgets/card_highlight.dart'; +import 'package:fluent_ui/fluent_ui.dart' hide Page; + +import '../../widgets/page.dart'; + +class RevealFocusPage extends Page { + final FocusNode focus = FocusNode(); + + @override + Widget build(BuildContext context) { + final theme = FluentTheme.of(context); + return ScaffoldPage.withPadding( + header: PageHeader( + title: const Text('Reveal Focus'), + commandBar: Button( + child: const Text('Focus'), + onPressed: () => focus.requestFocus(), + ), + ), + content: Column( + children: [ + description( + content: const Text( + 'Reveal Focus is a lighting effect for 10-foot experiences, such ' + 'as Xbox One and television screens. It animates the border of ' + 'focusable elements, such as buttons, when the user moves gamepad ' + 'or keyboard focus to them. It\'s turned off by default, but ' + 'it\'s simple to enable.', + ), + ), + subtitle(content: const Text('Enabling reveal focus')), + CardHighlight( + codeSnippet: '''FocusTheme( + data: FocusThemeData( + borderRadius: BorderRaidus.zero, + glowFactor: 4.0, + ), + child: ..., +)''', + child: Center( + child: FocusTheme( + data: FocusThemeData( + borderRadius: BorderRadius.zero, + // glowColor: theme.accentColor.withOpacity(0.2), + glowFactor: 4.0, + primaryBorder: BorderSide( + width: 2.0, + color: theme.inactiveColor, + ), + ), + child: Wrap( + runSpacing: 10.0, + spacing: 10.0, + alignment: WrapAlignment.center, + children: [ + buildCard(focus), + buildCard(), + buildCard(), + buildCard(), + ], + ), + ), + ), + ), + ], + ), + ); + } + + Widget buildCard([FocusNode? node]) { + final color = + Colors.accentColors[Random().nextInt(Colors.accentColors.length - 1)]; + return HoverButton( + focusNode: node, + onPressed: () {}, + builder: (context, states) { + return FocusBorder( + focused: states.isFocused, + useStackApproach: false, + child: Card( + backgroundColor: color, + child: const SizedBox( + width: 50.0, + height: 50.0, + ), + ), + ); + }, + ); + } +} diff --git a/example/lib/widgets/page.dart b/example/lib/widgets/page.dart index 846d41e2f..6f929f094 100644 --- a/example/lib/widgets/page.dart +++ b/example/lib/widgets/page.dart @@ -16,30 +16,6 @@ abstract class Page { func(); _controller.add(null); } -} - -int _pageIndex = -1; - -abstract class ScrollablePage extends Page { - ScrollablePage() : super(); - - final scrollController = ScrollController(); - Widget buildHeader(BuildContext context) => const SizedBox.shrink(); - - Widget buildBottomBar(BuildContext context) => const SizedBox.shrink(); - - List buildScrollable(BuildContext context); - - @override - Widget build(BuildContext context) { - return ScaffoldPage.scrollable( - key: PageStorageKey(_pageIndex), - scrollController: scrollController, - header: buildHeader(context), - children: buildScrollable(context), - bottomBar: buildBottomBar(context), - ); - } Widget description({required Widget content}) { return Builder(builder: (context) { @@ -66,6 +42,30 @@ abstract class ScrollablePage extends Page { } } +int _pageIndex = -1; + +abstract class ScrollablePage extends Page { + ScrollablePage() : super(); + + final scrollController = ScrollController(); + Widget buildHeader(BuildContext context) => const SizedBox.shrink(); + + Widget buildBottomBar(BuildContext context) => const SizedBox.shrink(); + + List buildScrollable(BuildContext context); + + @override + Widget build(BuildContext context) { + return ScaffoldPage.scrollable( + key: PageStorageKey(_pageIndex), + scrollController: scrollController, + header: buildHeader(context), + children: buildScrollable(context), + bottomBar: buildBottomBar(context), + ); + } +} + class EmptyPage extends Page { final Widget? child; From e2afd21a413db552873eb70998141d31eca2d5d9 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Fri, 26 Aug 2022 18:50:55 -0300 Subject: [PATCH 7/7] Update changelog --- CHANGELOG.md | 3 +++ example/lib/widgets/material_equivalents.dart | 2 +- test/label_test.dart | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7786ab439..13f8e1c23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ Date format: DD/MM/YYYY - Added `ComboBoxFormField` and `EditableComboBoxFormField` ([#373](https://github.com/bdlukaa/fluent_ui/issues/373)) - `Combobox.comboboxColor` is now correctly applied ([#468](https://github.com/bdlukaa/fluent_ui/issues/468)) - Implemented `PaneItemExpander` ([#299](https://github.com/bdlukaa/fluent_ui/pull/299)) +- `TimePicker` and `DatePicker` popup now needs a minimum width of 260 ([#494](https://github.com/bdlukaa/fluent_ui/pull/494)) +- Correctly align `NavigationAppBar` content ([#494](https://github.com/bdlukaa/fluent_ui/pull/494)) +- **BREAKING** Added `InfoLabel.rich`. `InfoLabel` is no longer a constant contructor ([#494](https://github.com/bdlukaa/fluent_ui/pull/494)) ## [4.0.0-pre.3] - Top navigation and auto suggestions - [13/08/2022] diff --git a/example/lib/widgets/material_equivalents.dart b/example/lib/widgets/material_equivalents.dart index eb68ffb8d..97cbe465f 100644 --- a/example/lib/widgets/material_equivalents.dart +++ b/example/lib/widgets/material_equivalents.dart @@ -38,7 +38,7 @@ class _MaterialEquivalentsState extends State { Widget build(BuildContext context) { List> children = [ [ - const Text('TextButton'), + const Text('Button'), Button( child: const Text('Content'), onPressed: () {}, diff --git a/test/label_test.dart b/test/label_test.dart index 5ba3388bd..c7cccfe1e 100644 --- a/test/label_test.dart +++ b/test/label_test.dart @@ -17,6 +17,9 @@ void main() { ), ); - expect(tester.widget(find.text('Label text')).style, labelStyle); + expect( + tester.widget(find.text('Label text')).textSpan?.style, + labelStyle, + ); }); }