From c1ce121ea16369df82b2f50d3a2e5059e2fc820d Mon Sep 17 00:00:00 2001 From: Luke Date: Mon, 18 Mar 2024 12:47:15 +0000 Subject: [PATCH 1/7] chore: update contributing --- CONTRIBUTING | 27 ------------------------- CONTRIBUTING.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 27 deletions(-) delete mode 100644 CONTRIBUTING create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING b/CONTRIBUTING deleted file mode 100644 index 2ccbf54b..00000000 --- a/CONTRIBUTING +++ /dev/null @@ -1,27 +0,0 @@ -# Getting Involved - -Thank you for your interest in this project. We'd love to see your contributions. There are just few small guidelines you need to follow. -Please note we have a code of conduct, please follow it in all your interactions with the project. - -## Opening an issue - -If you've noticed a bug or you have a suggestion for a new feature, please go ahead and open an issue in this project. Please do include as much information as possible. - -Please file issues before doing substantial work; this will ensure that others don't duplicate the work and that there's a chance to discuss any design issues. - -## Making a code change - -We're always open to pull requests, but these should be small and clearly described so that we can understand what you're trying to do. - -When you're ready to start coding, fork the needed repository to your own GitHub account and make your changes in a new branch. Once you're happy, open a pull request and explain what the change is and why you think we should include it in our project. - -## Code reviews - -All submissions, including submissions by project members, require review. We use GitHub pull requests (PRs) for this purpose. Consult [GitHub Help](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) for more information on using pull requests. - -Before a PR can be reviewed, ensure you have done the following, and fixed any issues that may arise: - -- Ensure branch is up to date `git rebase main` -- Check formatting: `flutter format .` -- Run static analyses: `flutter analyze` -- Run unit-tests: `flutter test` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..4b53985d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,53 @@ +# Getting Involved + +Thank you for your interest in this project. We'd love to see your contributions. There are just few small guidelines you need to follow. +Please note we have a code of conduct, please follow it in all your interactions with the project. + +## Opening an issue + +If you've noticed a bug or you have a suggestion for a new feature, please go ahead and open an issue in this project. Please do include as much information as possible. + +Please file issues before doing substantial work; this will ensure that others don't duplicate the work and that there's a chance to discuss any design issues. + +## Making a code change + +We're always open to pull requests, but these should be small and clearly described so that we can understand what you're trying to do. + +When you're ready to start coding, fork the needed repository to your own GitHub account and make your changes in a new branch. Once you're happy, open a pull request and explain what the change is and why you think we should include it in our project. + +If the change is a bug fix, try to create a test that aligns with the bug. + +### Creating a new component + +We want the designs to be the source of truth for this repository, so new components will only be accepted if they are with the design files. + +New components should use all tokens matching the design, and should not use hardcoded values for color, spacing, or radius. This ensures that changes made to these fundamental tokens are reflected throughout the library. + +All components should have inline [dartdoc](https://dart.dev/tools/dart-doc) documentation on public functions and variables. This is enforced by the lint rules. + +To demonstrate a component, we need to create 2 examples: firstly in the zeta_flutter example app and secondly in widgetbook. + +Example app should show basic examples to compare against the designs and is typically used by developers whilst building out components. + +The widgetbook is used by the wider team to review components. We should attempt to show the full functionality of a component in a single Widgetbook instance. We have some helper functions for building knobs for icons and rounded bool in [utils.dart](./example/widgetbook/utils/utils.dart). +For more information on widgetbook, [read the docs](https://docs.widgetbook.io/). + +We should also create a test for each widget created. + +## Code reviews + +All submissions, including submissions by project members, require review. We use GitHub pull requests (PRs) for this purpose. Consult [GitHub Help](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) for more information on using pull requests. + +Before a PR can be reviewed, ensure you have done the following, and fixed any issues that may arise: + +- Ensure branch is up to date `git rebase main` +- Check formatting: `flutter format .` +- Run static analyses: `flutter analyze` +- Run unit-tests: `flutter test` + +All PRs should be titled according to [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), as the branch will be squashed, so the PR title will become the commit message. +Examples: + +- `feat(X):` for new features +- `fix(x):` for bug fixes +- `chore(x):` for admin / chores. From 20d3c68721890617cefab10605a3a0a7179986fd Mon Sep 17 00:00:00 2001 From: ahmed-osman3 <99483750+ahmed-osman3@users.noreply.github.com> Date: Tue, 19 Mar 2024 16:33:59 +0000 Subject: [PATCH 2/7] fix: Fix button group immutability (#1) * Fix errors * fix copywith function * [automated commit] lint format and import sort --------- Co-authored-by: Osman Co-authored-by: github-actions --- lib/src/components/buttons/button_group.dart | 133 ++++++++++++++----- 1 file changed, 98 insertions(+), 35 deletions(-) diff --git a/lib/src/components/buttons/button_group.dart b/lib/src/components/buttons/button_group.dart index 52b8ea20..729c3a50 100644 --- a/lib/src/components/buttons/button_group.dart +++ b/lib/src/components/buttons/button_group.dart @@ -36,18 +36,21 @@ class ZetaButtonGroup extends StatelessWidget { /// Returns [ZetaGroupButton]s with there appropriate styling. List getButtons() { - for (final element in buttons) { - element - .._isInitial = element._isFinal = false - .._isLarge = isLarge - .._rounded = rounded - .._isInverse = isInverse; + final List mappedButtons = []; + + for (final (index, button) in buttons.indexed) { + mappedButtons.add( + button.copyWith( + large: isLarge, + inverse: isInverse, + round: rounded, + fin: index == buttons.length - 1, + initial: index == 0, + ), + ); } - buttons.first._isInitial = true; - buttons.last._isFinal = true; - - return buttons; + return mappedButtons; } @override @@ -63,34 +66,59 @@ class ZetaButtonGroup extends StatelessWidget { // TODO(UX-854): Create country variant. /// Group Button item -// ignore: must_be_immutable class ZetaGroupButton extends StatefulWidget { - /// Constructs [ZetaGroupButton] - ZetaGroupButton({ + + /// Public Constructor for [ZetaGroupButton] + const ZetaGroupButton({ super.key, this.label, this.icon, this.onPressed, this.dropdown, + }) : isFinal = false, + isInitial = false, + isInverse = false, + isLarge = true, + rounded = true; + /// Private constructor + const ZetaGroupButton._({ + super.key, + this.label, + this.icon, + this.onPressed, + this.dropdown, + required this.isFinal, + required this.isInitial, + required this.isInverse, + required this.isLarge, + required this.rounded, }); /// Constructs dropdown group button - ZetaGroupButton.dropdown({ + const ZetaGroupButton.dropdown({ super.key, required this.onPressed, required this.dropdown, this.icon, this.label, - }); + }) : isFinal = false, + isInitial = false, + isInverse = false, + isLarge = true, + rounded = true; ///Constructs group button with icon - ZetaGroupButton.icon({ + const ZetaGroupButton.icon({ super.key, required this.icon, this.dropdown, this.onPressed, this.label, - }); + }) : isFinal = false, + isInitial = false, + isInverse = false, + isLarge = true, + rounded = true; /// Label for [ZetaGroupButton]. final String? label; @@ -105,29 +133,43 @@ class ZetaGroupButton extends StatefulWidget { final Widget? dropdown; ///If [ZetaGroupButton] is large. - bool _isLarge = false; + final bool isLarge; ///If [ZetaGroupButton] is rounded. - bool _rounded = false; + final bool rounded; /// If [ZetaGroupButton] is the first button in its list. - bool _isInitial = false; + final bool isInitial; /// If [ZetaGroupButton] is the final button in its list. - bool _isFinal = false; + final bool isFinal; - bool _isInverse = false; + /// If [ZetaGroupButton] is inverse. + + final bool isInverse; @override State createState() => _ZetaGroupButtonState(); /// Returns copy of [ZetaGroupButton] with fields. - ZetaGroupButton copyWith({bool? isFinal, bool? isInitial}) { - return ZetaGroupButton( + ZetaGroupButton copyWith({ + bool? fin, + bool? initial, + bool? large, + bool? round, + bool? inverse, + }) { + return ZetaGroupButton._( key: key, label: label, icon: icon, onPressed: onPressed, + dropdown: dropdown, + isFinal: fin ?? isFinal, + isInitial: initial ?? isInitial, + isLarge: large ?? isLarge, + rounded: round ?? rounded, + isInverse: inverse ?? isInverse, ); } @@ -137,7 +179,12 @@ class ZetaGroupButton extends StatefulWidget { properties ..add(StringProperty('Label', label)) ..add(DiagnosticsProperty('icon', icon)) - ..add(ObjectFlagProperty.has('onPressed', onPressed)); + ..add(ObjectFlagProperty.has('onPressed', onPressed)) + ..add(DiagnosticsProperty('isInitial', isInitial)) + ..add(DiagnosticsProperty('isLarge', isLarge)) + ..add(DiagnosticsProperty('rounded', rounded)) + ..add(DiagnosticsProperty('isFinal', isFinal)) + ..add(DiagnosticsProperty('isInverse', isInverse)); } } @@ -156,11 +203,19 @@ class _ZetaGroupButtonState extends State { }); } + @override + void didUpdateWidget(ZetaGroupButton oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.onPressed != widget.onPressed) { + setState(() {}); + } + } + @override Widget build(BuildContext context) { final colors = Zeta.of(context).colors; - final borderType = widget._rounded ? ZetaWidgetBorder.rounded : ZetaWidgetBorder.sharp; + final borderType = widget.rounded ? ZetaWidgetBorder.rounded : ZetaWidgetBorder.sharp; final BorderSide borderSide = _getBorderSide(controller.value, colors, false); @@ -172,7 +227,7 @@ class _ZetaGroupButtonState extends State { bottom: borderSide, right: controller.value.contains(MaterialState.focused) ? BorderSide(color: colors.blue.shade50, width: 2) - : (widget._isFinal) + : (widget.isFinal) ? borderSide : BorderSide.none, ), @@ -190,7 +245,10 @@ class _ZetaGroupButtonState extends State { if (widget.icon != null) Icon(widget.icon), Text(widget.label!), if (widget.dropdown != null) // TODO(UX-1006): Dropdown - Icon(widget._rounded ? ZetaIcons.expand_more_round : ZetaIcons.expand_more_sharp, size: 20), + Icon( + widget.rounded ? ZetaIcons.expand_more_round : ZetaIcons.expand_more_sharp, + size: 20, + ), ], ).paddingAll(_padding), ), @@ -198,7 +256,7 @@ class _ZetaGroupButtonState extends State { ); } - double get _padding => widget._isLarge ? ZetaSpacing.x4 : ZetaSpacing.x3; + double get _padding => widget.isLarge ? ZetaSpacing.x4 : ZetaSpacing.x3; BorderSide _getBorderSide( Set states, @@ -208,7 +266,7 @@ class _ZetaGroupButtonState extends State { if (states.contains(MaterialState.focused)) { return BorderSide(color: colors.blue.shade50, width: ZetaSpacing.x0_5); } - if (widget._isInverse) return BorderSide(color: colors.black); + if (widget.isInverse) return BorderSide(color: colors.black); if (states.contains(MaterialState.disabled)) { return BorderSide(color: colors.cool.shade40); } @@ -218,13 +276,13 @@ class _ZetaGroupButtonState extends State { } BorderRadius _getRadius(ZetaWidgetBorder borderType) { - if (widget._isInitial) { + if (widget.isInitial) { return borderType.radius.copyWith( topRight: Radius.zero, bottomRight: Radius.zero, ); } - if (widget._isFinal) { + if (widget.isFinal) { return borderType.radius.copyWith( topLeft: Radius.zero, bottomLeft: Radius.zero, @@ -241,7 +299,7 @@ class _ZetaGroupButtonState extends State { ), ), backgroundColor: MaterialStateProperty.resolveWith((states) { - if (widget._isInverse) return colors.cool.shade100; + if (widget.isInverse) return colors.cool.shade100; if (states.contains(MaterialState.disabled)) { return colors.surfaceDisabled; @@ -258,7 +316,7 @@ class _ZetaGroupButtonState extends State { if (states.contains(MaterialState.disabled)) { return colors.textDisabled; } - if (widget._isInverse) return colors.cool.shade100.onColor; + if (widget.isInverse) return colors.cool.shade100.onColor; return colors.textDefault; }), elevation: const MaterialStatePropertyAll(0), @@ -269,6 +327,11 @@ class _ZetaGroupButtonState extends State { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(DiagnosticsProperty('controller', controller)); + properties.add( + DiagnosticsProperty( + 'controller', + controller, + ), + ); } } From 985576b8d8131d6cceccc164ab3111edc3a1796e Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 19 Mar 2024 16:35:41 +0000 Subject: [PATCH 3/7] [automated commit] lint format and import sort --- lib/src/components/buttons/button_group.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/components/buttons/button_group.dart b/lib/src/components/buttons/button_group.dart index 729c3a50..4a71ba17 100644 --- a/lib/src/components/buttons/button_group.dart +++ b/lib/src/components/buttons/button_group.dart @@ -67,7 +67,6 @@ class ZetaButtonGroup extends StatelessWidget { /// Group Button item class ZetaGroupButton extends StatefulWidget { - /// Public Constructor for [ZetaGroupButton] const ZetaGroupButton({ super.key, @@ -80,6 +79,7 @@ class ZetaGroupButton extends StatefulWidget { isInverse = false, isLarge = true, rounded = true; + /// Private constructor const ZetaGroupButton._({ super.key, From 98abb405c86732cbf4ced47a2de39fb7bd71fef4 Mon Sep 17 00:00:00 2001 From: Luke Walton Date: Wed, 20 Mar 2024 09:45:35 +0000 Subject: [PATCH 4/7] update on-main to push to firebase (#3) --- .github/workflows/on-main.yml | 19 +++++++++++++++++++ .github/workflows/pull-request.yml | 3 --- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/workflows/on-main.yml b/.github/workflows/on-main.yml index 615b41fb..9c8f5763 100644 --- a/.github/workflows/on-main.yml +++ b/.github/workflows/on-main.yml @@ -45,3 +45,22 @@ jobs: git add -A git commit -m '[automated commit] lint format and import sort' git push -f + deploy-qa-demo: + name: Deploy preview version of the storybook on firebase + needs: code-quality + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + - name: Setup flutter + run: flutter pub get + - name: Build example app + run: | + cd example + flutter build web -t widgetbook/main.dart -o ../build --no-tree-shake-icons + - uses: FirebaseExtended/action-hosting-deploy@v0 + with: + repoToken: "${{ secrets.GITHUB_TOKEN }}" + firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_ZETA_DS }}" + projectId: zeta-ds + channelId: "qa" diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 31513970..5c28f0c6 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -87,9 +87,6 @@ jobs: with: ref: ${{ github.head_ref }} - uses: subosito/flutter-action@v2 - with: - flutter-version: "3.19.x" - channel: "stable" - name: Setup flutter run: flutter pub get - name: Build example app From e63476adf016d60f4f2bd4eab1b80bbf21f4d8f3 Mon Sep 17 00:00:00 2001 From: Luke Walton Date: Wed, 20 Mar 2024 10:44:53 +0000 Subject: [PATCH 5/7] ci: move firebase to flutter main host for qa (#4) --- .firebaserc | 9 +++++++++ .github/workflows/on-main.yml | 3 +-- .github/workflows/pull-request.yml | 2 -- firebase.json | 1 + 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.firebaserc b/.firebaserc index 91581093..6ff5098d 100644 --- a/.firebaserc +++ b/.firebaserc @@ -1,5 +1,14 @@ { "projects": { "default": "zeta-ds" + }, + "targets": { + "zeta-ds": { + "hosting": { + "flutter": [ + "zeta-flutter-main" + ] + } + } } } \ No newline at end of file diff --git a/.github/workflows/on-main.yml b/.github/workflows/on-main.yml index 9c8f5763..bb3b9aa4 100644 --- a/.github/workflows/on-main.yml +++ b/.github/workflows/on-main.yml @@ -62,5 +62,4 @@ jobs: with: repoToken: "${{ secrets.GITHUB_TOKEN }}" firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_ZETA_DS }}" - projectId: zeta-ds - channelId: "qa" + channelId: "live" diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 5c28f0c6..dab856c5 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -97,6 +97,4 @@ jobs: with: repoToken: "${{ secrets.GITHUB_TOKEN }}" firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_ZETA_DS }}" - expires: 7d - projectId: zeta-ds channelId: "pr-${{ github.event.number }}-${{ github.event.pull_request.head.ref }}" diff --git a/firebase.json b/firebase.json index 4a130f50..bf8f2069 100644 --- a/firebase.json +++ b/firebase.json @@ -1,6 +1,7 @@ { "hosting": { "public": "build", + "target": "flutter", "rewrites": [ { "source": "**", From 31031398e809fe110072929a7584c16eb9521613 Mon Sep 17 00:00:00 2001 From: Simeon Dimitrov Date: Thu, 21 Mar 2024 09:30:12 +0200 Subject: [PATCH 6/7] feat: Add List Item --- example/lib/home.dart | 2 + .../pages/components/list_item_example.dart | 115 ++++++++++ example/widgetbook/main.dart | 8 +- .../components/list_item_widgetbook.dart | 49 ++++ lib/src/components/list_item/list_item.dart | 213 ++++++++++++++++++ lib/zeta_flutter.dart | 1 + 6 files changed, 387 insertions(+), 1 deletion(-) create mode 100644 example/lib/pages/components/list_item_example.dart create mode 100644 example/widgetbook/pages/components/list_item_widgetbook.dart create mode 100644 lib/src/components/list_item/list_item.dart diff --git a/example/lib/home.dart b/example/lib/home.dart index 02538b11..85fb9327 100644 --- a/example/lib/home.dart +++ b/example/lib/home.dart @@ -10,6 +10,7 @@ import 'package:zeta_example/pages/components/button_example.dart'; import 'package:zeta_example/pages/components/checkbox_example.dart'; import 'package:zeta_example/pages/components/chip_example.dart'; import 'package:zeta_example/pages/components/dialpad_example.dart'; +import 'package:zeta_example/pages/components/list_item_example.dart'; import 'package:zeta_example/pages/components/navigation_bar_example.dart'; import 'package:zeta_example/pages/theme/color_example.dart'; import 'package:zeta_example/pages/components/password_input_example.dart'; @@ -38,6 +39,7 @@ final List components = [ Component(ButtonExample.name, (context) => const ButtonExample()), Component(CheckBoxExample.name, (context) => const CheckBoxExample()), Component(ChipExample.name, (context) => const ChipExample()), + Component(ListItemExample.name, (context) => const ListItemExample()), Component(NavigationBarExample.name, (context) => const NavigationBarExample()), Component(PasswordInputExample.name, (context) => const PasswordInputExample()), Component(ProgressExample.name, (context) => const ProgressExample()), diff --git a/example/lib/pages/components/list_item_example.dart b/example/lib/pages/components/list_item_example.dart new file mode 100644 index 00000000..9cc60696 --- /dev/null +++ b/example/lib/pages/components/list_item_example.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +class ListItemExample extends StatefulWidget { + static const String name = 'ListItem'; + + const ListItemExample({super.key}); + + @override + State createState() => _ListItemExampleState(); +} + +class _ListItemExampleState extends State { + bool _isCheckBoxEnabled = false; + bool _isSelected = true; + + _onDefaultListItemTap() { + setState(() => _isCheckBoxEnabled = !_isCheckBoxEnabled); + } + + @override + Widget build(BuildContext context) { + final zetaColors = Zeta.of(context).colors; + + return ExampleScaffold( + name: ListItemExample.name, + child: Container( + color: zetaColors.surfaceSecondary, + child: SingleChildScrollView( + child: Column( + children: [ + // List Item with descriptor + Padding( + padding: const EdgeInsets.only(top: ZetaSpacing.x4), + child: ZetaListItem( + dense: true, + leading: Container( + width: 48, + height: 48, + decoration: BoxDecoration(borderRadius: ZetaRadius.rounded), + child: Placeholder(), + ), + subtitle: Text("Descriptor"), + title: Text("List Item"), + trailing: ZetaCheckbox( + value: _isCheckBoxEnabled, + onChanged: (_) => _onDefaultListItemTap(), + ), + onTap: _onDefaultListItemTap, + ), + ), + + // Enabled + Padding( + padding: const EdgeInsets.only(top: ZetaSpacing.l), + child: Text( + "Enabled", + style: ZetaTextStyles.titleLarge, + ), + ), + Padding( + padding: const EdgeInsets.only(top: ZetaSpacing.m), + child: ZetaListItem(title: Text("List Item")), + ), + + // Selected + Padding( + padding: const EdgeInsets.only(top: ZetaSpacing.l), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "Selected", + style: ZetaTextStyles.titleLarge, + ), + ), + ), + Padding( + padding: const EdgeInsets.only(top: ZetaSpacing.m), + child: ZetaListItem( + title: Text("List Item"), + selected: _isSelected, + trailing: _isSelected + ? Icon( + ZetaIcons.check_mark_sharp, + color: zetaColors.primary, + ) + : null, + onTap: () => setState(() => _isSelected = !_isSelected), + ), + ), + + // Disabled + Padding( + padding: const EdgeInsets.only(top: ZetaSpacing.l), + child: Text( + "Disabled", + style: ZetaTextStyles.titleLarge, + ), + ), + Padding( + padding: const EdgeInsets.only(top: ZetaSpacing.m), + child: ZetaListItem( + title: Text("List Item"), + enabled: false, + onTap: () {}, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/example/widgetbook/main.dart b/example/widgetbook/main.dart index c6d2620d..eb6dcac9 100644 --- a/example/widgetbook/main.dart +++ b/example/widgetbook/main.dart @@ -14,6 +14,7 @@ import 'pages/components/checkbox_widgetbook.dart'; import 'pages/components/chip_widgetbook.dart'; import 'pages/components/dial_pad_widgetbook.dart'; import 'pages/components/in_page_banner_widgetbook.dart'; +import 'pages/components/list_item_widgetbook.dart'; import 'pages/components/navigation_bar_widgetbook.dart'; import 'pages/components/password_input_widgetbook.dart'; import 'pages/components/progress_widgetbook.dart'; @@ -75,7 +76,12 @@ class HotReload extends StatelessWidget { ), WidgetbookUseCase(name: 'Password Input', builder: (context) => passwordInputUseCase(context)), WidgetbookUseCase(name: 'Content', builder: (context) => bottomSheetContentUseCase(context)), - WidgetbookUseCase(name: 'Dial Pad', builder: (context) => dialPadUseCase(context)), + WidgetbookUseCase( + name: 'Dial Pad', + builder: (context) => dialPadUseCase(context)), + WidgetbookUseCase( + name: 'List Item', + builder: (context) => listItemUseCase(context)), WidgetbookUseCase(name: 'Navigation Bar', builder: (context) => navigationBarUseCase(context)), WidgetbookComponent( name: 'Progress', diff --git a/example/widgetbook/pages/components/list_item_widgetbook.dart b/example/widgetbook/pages/components/list_item_widgetbook.dart new file mode 100644 index 00000000..40c403a1 --- /dev/null +++ b/example/widgetbook/pages/components/list_item_widgetbook.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../test/test_components.dart'; + +Widget listItemUseCase(BuildContext context) { + return WidgetbookTestWidget( + widget: StatefulBuilder( + builder: (context, setState) { + final subtitle = + context.knobs.stringOrNull(label: 'Descriptor', initialValue: null); + + final trailing = + context.knobs.boolean(label: 'Trailing', initialValue: false) + ? Checkbox(value: false, onChanged: (_) {}) + : null; + + final leading = + context.knobs.boolean(label: 'Leading', initialValue: false) + ? Container( + width: 48, + height: 48, + decoration: BoxDecoration(borderRadius: ZetaRadius.rounded), + child: Placeholder(), + ) + : null; + + return ZetaListItem( + dense: context.knobs.boolean(label: 'Dense', initialValue: false), + enabled: context.knobs.boolean(label: 'Enabled', initialValue: true), + enabledDivider: context.knobs.boolean( + label: 'Enabled Divider', + initialValue: true, + ), + selected: + context.knobs.boolean(label: 'Selected', initialValue: true), + leading: leading, + title: Text( + context.knobs.string(label: 'Title', initialValue: 'List Item'), + ), + subtitle: subtitle != null ? Text(subtitle) : null, + trailing: trailing, + onTap: () {}, + ); + }, + ), + ); +} diff --git a/lib/src/components/list_item/list_item.dart b/lib/src/components/list_item/list_item.dart new file mode 100644 index 00000000..e7d143ec --- /dev/null +++ b/lib/src/components/list_item/list_item.dart @@ -0,0 +1,213 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import '../../../zeta_flutter.dart'; + +/// A single row that typically contains some text as well as a leading or trailing widgets. +class ZetaListItem extends StatelessWidget { + /// Creates a [ZetaListItem]. + const ZetaListItem({ + required this.title, + this.dense = false, + this.enabled = true, + this.enabledDivider = true, + this.leading, + this.onTap, + this.selected = false, + this.subtitle, + this.trailing, + super.key, + }); + + /// Dense list items have less space between widgets and use smaller [TextStyle] + final bool dense; + + /// Whether this [ZetaListItem] is interactive. + /// If false the [onTap] callback is inoperative. + final bool enabled; + + /// Whether to apply divider. Normally at the bottom of the [ZetaListItem]. + final bool enabledDivider; + + /// A Widget to display before the title; + final Widget? leading; + + /// Called when user taps on the [ZetaListItem]. + final VoidCallback? onTap; + + /// Applies selected styles. If selected is true trailing mu + final bool selected; + + /// Additional content displayed over the title. + /// Typically a [Text] widget. + final Widget? subtitle; + + /// The primary content of the [ZetaListItem]. + final Widget title; + + /// A widget to display after the title. + final Widget? trailing; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('dense', dense)) + ..add(DiagnosticsProperty('enabled', enabled)) + ..add(DiagnosticsProperty('enabledDivider', enabledDivider)) + ..add(DiagnosticsProperty('leading', leading)) + ..add(ObjectFlagProperty.has('onTap', onTap)) + ..add(DiagnosticsProperty('selected', selected)) + ..add(DiagnosticsProperty('subtitle', subtitle)) + ..add(DiagnosticsProperty('title', title)) + ..add(DiagnosticsProperty('trailing', trailing)); + } + + TextStyle get _titleTextStyle => + dense ? ZetaTextStyles.titleSmall : ZetaTextStyles.titleMedium; + + @override + Widget build(BuildContext context) { + final zetaColors = Zeta.of(context).colors; + + return _ListItemContainer( + enabled: enabled, + selected: selected, + onTap: onTap, + dense: dense, + enabledDivider: enabledDivider, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Row( + children: [ + if (leading != null) + Padding( + padding: EdgeInsets.only( + right: dense ? ZetaSpacing.x2 : ZetaSpacing.x4, + ), + child: leading, + ), + Flexible( + child: Padding( + padding: EdgeInsets.symmetric( + vertical: dense ? 0.0 : ZetaSpacing.x4, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (subtitle != null) + DefaultTextStyle( + style: ZetaTextStyles.titleSmall.copyWith( + color: zetaColors.textSubtle, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + child: subtitle ?? const SizedBox(), + ), + DefaultTextStyle( + style: _titleTextStyle.copyWith( + color: enabled + ? zetaColors.textDefault + : zetaColors.textSubtle, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + child: title, + ), + ], + ), + ), + ), + ], + ), + ), + if (trailing != null) + Padding( + padding: EdgeInsets.only( + left: dense ? ZetaSpacing.x2 : ZetaSpacing.x4, + ), + child: trailing, + ), + if (trailing == null && selected && enabled) + Padding( + padding: EdgeInsets.only( + left: dense ? ZetaSpacing.x2 : ZetaSpacing.x4, + ), + child: Icon( + ZetaIcons.check_mark_round, + color: zetaColors.blue.shade60, + ), + ), + ], + ), + ); + } +} + +class _ListItemContainer extends StatelessWidget { + const _ListItemContainer({ + required this.child, + required this.dense, + required this.enabled, + required this.enabledDivider, + required this.onTap, + required this.selected, + }); + + final Widget child; + final bool dense; + final bool enabled; + final bool enabledDivider; + final VoidCallback? onTap; + final bool selected; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('child', child)) + ..add(DiagnosticsProperty('enabled', enabled)) + ..add(DiagnosticsProperty('selected', selected)) + ..add(ObjectFlagProperty.has('onTap', onTap)) + ..add(DiagnosticsProperty('dense', dense)) + ..add(DiagnosticsProperty('enabledDivider', enabledDivider)); + } + + @override + Widget build(BuildContext context) { + final zetaColors = Zeta.of(context).colors; + + return AbsorbPointer( + absorbing: !enabled, + child: Material( + color: enabled + ? selected + ? zetaColors.blue.shade10 + : zetaColors.white + : zetaColors.surfaceDisabled, + child: InkWell( + onTap: enabled ? onTap : null, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: dense ? ZetaSpacing.x4 : ZetaSpacing.x8, + vertical: dense ? ZetaSpacing.x2 : ZetaSpacing.x4, + ), + decoration: BoxDecoration( + border: enabled && enabledDivider + ? Border( + bottom: BorderSide( + color: selected + ? zetaColors.blue.shade40 + : zetaColors.borderDefault, + ), + ) + : null, + ), + child: child, + ), + ), + ), + ); + } +} diff --git a/lib/zeta_flutter.dart b/lib/zeta_flutter.dart index 75d941d8..964e8d14 100644 --- a/lib/zeta_flutter.dart +++ b/lib/zeta_flutter.dart @@ -23,6 +23,7 @@ export 'src/components/buttons/icon_button.dart'; export 'src/components/checkbox/checkbox.dart'; export 'src/components/chips/chip.dart'; export 'src/components/dial_pad/dial_pad.dart'; +export 'src/components/list_item/list_item.dart'; export 'src/components/navigation bar/navigation_bar.dart'; export 'src/components/password/password_input.dart'; export 'src/components/progress/progress_bar.dart'; From 33fc6d0f70ccdfb50c8d0f71e3e7ca856b80f4d4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 21 Mar 2024 09:44:30 +0000 Subject: [PATCH 7/7] [automated commit] lint format and import sort --- example/widgetbook/main.dart | 8 ++--- .../components/list_item_widgetbook.dart | 30 ++++++++----------- lib/src/components/list_item/list_item.dart | 11 ++----- 3 files changed, 18 insertions(+), 31 deletions(-) diff --git a/example/widgetbook/main.dart b/example/widgetbook/main.dart index eb6dcac9..cee55d3b 100644 --- a/example/widgetbook/main.dart +++ b/example/widgetbook/main.dart @@ -76,12 +76,8 @@ class HotReload extends StatelessWidget { ), WidgetbookUseCase(name: 'Password Input', builder: (context) => passwordInputUseCase(context)), WidgetbookUseCase(name: 'Content', builder: (context) => bottomSheetContentUseCase(context)), - WidgetbookUseCase( - name: 'Dial Pad', - builder: (context) => dialPadUseCase(context)), - WidgetbookUseCase( - name: 'List Item', - builder: (context) => listItemUseCase(context)), + WidgetbookUseCase(name: 'Dial Pad', builder: (context) => dialPadUseCase(context)), + WidgetbookUseCase(name: 'List Item', builder: (context) => listItemUseCase(context)), WidgetbookUseCase(name: 'Navigation Bar', builder: (context) => navigationBarUseCase(context)), WidgetbookComponent( name: 'Progress', diff --git a/example/widgetbook/pages/components/list_item_widgetbook.dart b/example/widgetbook/pages/components/list_item_widgetbook.dart index 40c403a1..e545cf7c 100644 --- a/example/widgetbook/pages/components/list_item_widgetbook.dart +++ b/example/widgetbook/pages/components/list_item_widgetbook.dart @@ -8,23 +8,20 @@ Widget listItemUseCase(BuildContext context) { return WidgetbookTestWidget( widget: StatefulBuilder( builder: (context, setState) { - final subtitle = - context.knobs.stringOrNull(label: 'Descriptor', initialValue: null); + final subtitle = context.knobs.stringOrNull(label: 'Descriptor', initialValue: null); - final trailing = - context.knobs.boolean(label: 'Trailing', initialValue: false) - ? Checkbox(value: false, onChanged: (_) {}) - : null; + final trailing = context.knobs.boolean(label: 'Trailing', initialValue: false) + ? Checkbox(value: false, onChanged: (_) {}) + : null; - final leading = - context.knobs.boolean(label: 'Leading', initialValue: false) - ? Container( - width: 48, - height: 48, - decoration: BoxDecoration(borderRadius: ZetaRadius.rounded), - child: Placeholder(), - ) - : null; + final leading = context.knobs.boolean(label: 'Leading', initialValue: false) + ? Container( + width: 48, + height: 48, + decoration: BoxDecoration(borderRadius: ZetaRadius.rounded), + child: Placeholder(), + ) + : null; return ZetaListItem( dense: context.knobs.boolean(label: 'Dense', initialValue: false), @@ -33,8 +30,7 @@ Widget listItemUseCase(BuildContext context) { label: 'Enabled Divider', initialValue: true, ), - selected: - context.knobs.boolean(label: 'Selected', initialValue: true), + selected: context.knobs.boolean(label: 'Selected', initialValue: true), leading: leading, title: Text( context.knobs.string(label: 'Title', initialValue: 'List Item'), diff --git a/lib/src/components/list_item/list_item.dart b/lib/src/components/list_item/list_item.dart index e7d143ec..80cbdb1a 100644 --- a/lib/src/components/list_item/list_item.dart +++ b/lib/src/components/list_item/list_item.dart @@ -62,8 +62,7 @@ class ZetaListItem extends StatelessWidget { ..add(DiagnosticsProperty('trailing', trailing)); } - TextStyle get _titleTextStyle => - dense ? ZetaTextStyles.titleSmall : ZetaTextStyles.titleMedium; + TextStyle get _titleTextStyle => dense ? ZetaTextStyles.titleSmall : ZetaTextStyles.titleMedium; @override Widget build(BuildContext context) { @@ -107,9 +106,7 @@ class ZetaListItem extends StatelessWidget { ), DefaultTextStyle( style: _titleTextStyle.copyWith( - color: enabled - ? zetaColors.textDefault - : zetaColors.textSubtle, + color: enabled ? zetaColors.textDefault : zetaColors.textSubtle, ), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -197,9 +194,7 @@ class _ListItemContainer extends StatelessWidget { border: enabled && enabledDivider ? Border( bottom: BorderSide( - color: selected - ? zetaColors.blue.shade40 - : zetaColors.borderDefault, + color: selected ? zetaColors.blue.shade40 : zetaColors.borderDefault, ), ) : null,