diff --git a/examples/custom_theme_example/pubspec.lock b/examples/custom_theme_example/pubspec.lock index 67fd687b3..c6c9fc431 100644 --- a/examples/custom_theme_example/pubspec.lock +++ b/examples/custom_theme_example/pubspec.lock @@ -223,7 +223,7 @@ packages: path: "../../packages/widgetbook" relative: true source: path - version: "3.0.0-beta.3" + version: "3.0.0-beta.5" widgetbook_annotation: dependency: "direct main" description: @@ -231,6 +231,13 @@ packages: relative: true source: path version: "3.0.0-beta.3" + widgetbook_core: + dependency: "direct overridden" + description: + path: "../../packages/widgetbook_core" + relative: true + source: path + version: "0.0.1" widgetbook_models: dependency: "direct overridden" description: diff --git a/examples/widgetbook_annotation_example/lib/test-widgetbook.dart b/examples/widgetbook_annotation_example/lib/test-widgetbook.dart new file mode 100644 index 000000000..79e742636 --- /dev/null +++ b/examples/widgetbook_annotation_example/lib/test-widgetbook.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; +import 'package:meal_app/themes/dark_theme.dart'; +import 'package:meal_app/themes/light_theme.dart'; +import 'package:widgetbook/widgetbook.dart'; + +import 'app.dart'; + +void main() { + runApp(HotReload()); +} + +class HotReload extends StatelessWidget { + const HotReload({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Widgetbook.material( + appInfo: AppInfo( + name: 'Test App', + ), + addons: [ + CustomThemeAddon( + setting: ThemeSetting.firstAsSelected( + themes: [ + WidgetbookTheme( + name: 'Dark', + data: getDarkThemeData(), + ), + WidgetbookTheme( + name: 'Light', + data: getLightThemeData(), + ), + ], + ), + ), + TextScaleAddon( + setting: TextScaleSetting.firstAsSelected( + textScales: [ + 1.0, + 2.0, + 3.0, + ], + ), + ), + LocalizationAddon( + setting: LocalizationSetting( + locales: locales, + activeLocale: locales.first, + localizationsDelegates: delegates, + ), + ), + FrameAddon( + setting: FrameSetting.firstAsSelected( + frames: [ + WidgetbookFrame( + setting: DeviceSetting.firstAsSelected( + devices: [Apple.iPhone12], + ), + ), + NoFrame(), + DefaultDeviceFrame( + setting: DeviceSetting.firstAsSelected( + devices: [Apple.iPhone12], + ), + ), + ], + ), + ), + ], + children: [ + WidgetbookComponent( + name: 'Test Component', + isInitiallyExpanded: true, + useCases: [ + WidgetbookUseCase( + name: 'Test Use Case', + builder: (context) => const TestWidget(), + ), + ], + ), + WidgetbookCategory( + name: 'Test Category', + children: [ + WidgetbookFolder( + name: 'Test Folder', + isInitiallyExpanded: true, + children: [ + WidgetbookComponent( + name: 'Test Component', + isInitiallyExpanded: true, + useCases: [ + WidgetbookUseCase( + name: 'Test Use Case 1', + builder: (context) => + const TestWidget(text: 'Test Widget 1'), + ), + ], + ), + WidgetbookComponent( + name: 'Test Component', + isInitiallyExpanded: true, + useCases: [ + WidgetbookUseCase( + name: 'Test Use Case 2', + builder: (context) => + const TestWidget(text: 'Test Widget 2'), + ), + ], + ), + ], + ), + ], + ), + ], + ); + } +} + +class TestWidget extends StatelessWidget { + const TestWidget({ + super.key, + this.text = 'Test Widget', + }); + + final String text; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center(child: Text(text)), + ); + } +} diff --git a/examples/widgetbook_annotation_example/lib/test.dart b/examples/widgetbook_annotation_example/lib/test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/examples/widgetbook_annotation_example/lib/test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/examples/widgetbook_annotation_example/lib/themes/dark_theme.dart b/examples/widgetbook_annotation_example/lib/themes/dark_theme.dart index 1c1fcb7c6..2a8f09460 100644 --- a/examples/widgetbook_annotation_example/lib/themes/dark_theme.dart +++ b/examples/widgetbook_annotation_example/lib/themes/dark_theme.dart @@ -6,6 +6,8 @@ import 'package:widgetbook_annotation/widgetbook_annotation.dart'; @WidgetbookTheme(name: 'Dark', isDefault: true) ThemeData getDarkThemeData() => ThemeData( primarySwatch: Colors.blue, + scaffoldBackgroundColor: Colors.black, + brightness: Brightness.dark, ); ThemeData getDarkTheme(BuildContext context) { diff --git a/examples/widgetbook_annotation_example/pubspec.lock b/examples/widgetbook_annotation_example/pubspec.lock index fc115f481..680d6df80 100644 --- a/examples/widgetbook_annotation_example/pubspec.lock +++ b/examples/widgetbook_annotation_example/pubspec.lock @@ -244,7 +244,7 @@ packages: name: freezed_annotation url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.2.0" frontend_server_client: dependency: transitive description: @@ -314,7 +314,7 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "4.4.0" + version: "4.7.0" logging: dependency: transitive description: @@ -515,7 +515,7 @@ packages: path: "../../packages/widgetbook" relative: true source: path - version: "3.0.0-beta.3" + version: "3.0.0-beta.5" widgetbook_annotation: dependency: "direct main" description: @@ -523,6 +523,13 @@ packages: relative: true source: path version: "3.0.0-beta.3" + widgetbook_core: + dependency: "direct overridden" + description: + path: "../../packages/widgetbook_core" + relative: true + source: path + version: "0.0.1" widgetbook_generator: dependency: "direct dev" description: @@ -545,5 +552,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.17.0 <3.0.0" + dart: ">=2.18.0 <3.0.0" flutter: ">=3.3.0" diff --git a/examples/widgetbook_annotation_example/pubspec.yaml b/examples/widgetbook_annotation_example/pubspec.yaml index 145eab02e..b44fc1955 100644 --- a/examples/widgetbook_annotation_example/pubspec.yaml +++ b/examples/widgetbook_annotation_example/pubspec.yaml @@ -6,7 +6,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.18.0 <3.0.0" dependencies: bloc: ^7.0.0 diff --git a/packages/widgetbook/example/main.dart b/packages/widgetbook/example/main.dart index 1d59e4b4d..f81039a19 100644 --- a/packages/widgetbook/example/main.dart +++ b/packages/widgetbook/example/main.dart @@ -12,10 +12,10 @@ class HotreloadWidgetbook extends StatelessWidget { Widget build(BuildContext context) { return Widgetbook.material( addons: [], - categories: [ + children: [ WidgetbookCategory( name: 'widgets', - widgets: [ + children: [ WidgetbookComponent( name: 'Button', useCases: [ diff --git a/packages/widgetbook/lib/src/addons/frame_addon/frame_addon.dart b/packages/widgetbook/lib/src/addons/frame_addon/frame_addon.dart index 55da1f615..ff0829b2d 100644 --- a/packages/widgetbook/lib/src/addons/frame_addon/frame_addon.dart +++ b/packages/widgetbook/lib/src/addons/frame_addon/frame_addon.dart @@ -8,7 +8,6 @@ import 'package:widgetbook/src/addons/frame_addon/frame_provider.dart'; import 'package:widgetbook/src/addons/frame_addon/frame_selection_provider.dart'; import 'package:widgetbook/src/addons/models/models.dart'; import 'package:widgetbook/src/addons/widgets/widgets.dart'; -import 'package:widgetbook/src/navigation/preview_provider.dart'; import 'package:widgetbook/src/navigation/router.dart'; import 'package:widgetbook/widgetbook.dart'; diff --git a/packages/widgetbook/lib/src/app_info/app_info.dart b/packages/widgetbook/lib/src/app_info/app_info.dart index 4eee5a934..cda1d59b5 100644 --- a/packages/widgetbook/lib/src/app_info/app_info.dart +++ b/packages/widgetbook/lib/src/app_info/app_info.dart @@ -1,9 +1,2 @@ -class AppInfo { - AppInfo({ - required this.name, - }); - - /// The name of the app or project for which the Widgetbook is created. - /// This name will be displayed in the upper left corner. - final String name; -} +export './models/models.dart'; +export './providers/providers.dart'; diff --git a/packages/widgetbook/lib/src/app_info/models/app_info.dart b/packages/widgetbook/lib/src/app_info/models/app_info.dart new file mode 100644 index 000000000..4eee5a934 --- /dev/null +++ b/packages/widgetbook/lib/src/app_info/models/app_info.dart @@ -0,0 +1,9 @@ +class AppInfo { + AppInfo({ + required this.name, + }); + + /// The name of the app or project for which the Widgetbook is created. + /// This name will be displayed in the upper left corner. + final String name; +} diff --git a/packages/widgetbook/lib/src/app_info/models/models.dart b/packages/widgetbook/lib/src/app_info/models/models.dart new file mode 100644 index 000000000..f65ee39d1 --- /dev/null +++ b/packages/widgetbook/lib/src/app_info/models/models.dart @@ -0,0 +1 @@ +export './app_info.dart'; diff --git a/packages/widgetbook/lib/src/app_info/app_info_provider.dart b/packages/widgetbook/lib/src/app_info/providers/app_info_provider.dart similarity index 84% rename from packages/widgetbook/lib/src/app_info/app_info_provider.dart rename to packages/widgetbook/lib/src/app_info/providers/app_info_provider.dart index a16ddddb0..0ce2f42d2 100644 --- a/packages/widgetbook/lib/src/app_info/app_info_provider.dart +++ b/packages/widgetbook/lib/src/app_info/providers/app_info_provider.dart @@ -1,5 +1,5 @@ +import 'package:widgetbook/src/app_info/app_info.dart'; import 'package:widgetbook/src/state_change_notifier.dart'; -import 'package:widgetbook/widgetbook.dart'; class AppInfoProvider extends StateChangeNotifier { AppInfoProvider({required AppInfo state}) : super(state: state); diff --git a/packages/widgetbook/lib/src/app_info/providers/providers.dart b/packages/widgetbook/lib/src/app_info/providers/providers.dart new file mode 100644 index 000000000..d5f8733bd --- /dev/null +++ b/packages/widgetbook/lib/src/app_info/providers/providers.dart @@ -0,0 +1 @@ +export './app_info_provider.dart'; diff --git a/packages/widgetbook/lib/src/models/models.dart b/packages/widgetbook/lib/src/models/models.dart deleted file mode 100644 index bec589c10..000000000 --- a/packages/widgetbook/lib/src/models/models.dart +++ /dev/null @@ -1,5 +0,0 @@ -export 'package:widgetbook/src/app_info/app_info.dart'; -export 'package:widgetbook/src/models/organizers/organizers.dart'; -export 'package:widgetbook/src/models/organizers/widgetbook_category.dart'; -export 'package:widgetbook/src/models/organizers/widgetbook_use_case.dart'; -export 'package:widgetbook/src/models/organizers/widgetbook_widget.dart'; diff --git a/packages/widgetbook/lib/src/models/organizers/expandable_organizer.dart b/packages/widgetbook/lib/src/models/organizers/expandable_organizer.dart deleted file mode 100644 index e62d222db..000000000 --- a/packages/widgetbook/lib/src/models/organizers/expandable_organizer.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:widgetbook/src/models/organizers/organizers.dart'; - -/// ExpandableOrganizer is an abstract model which can host -/// WidgetElements and/or Folders -abstract class ExpandableOrganizer extends Organizer { - ExpandableOrganizer({ - required String name, - required this.isExpanded, - List? folders, - List? widgets, - }) : folders = folders ?? List.empty(), - widgets = widgets ?? List.empty(), - super( - name, - ); - - /// Used to implement collapsing and expanding of the folder tree. - bool isExpanded; - - /// The folders of one level in the folder tree. - /// Folders will be shown above widgets. - final List folders; - - /// The widgets of one level in the folder tree. - /// Widgets will be shown below folders; - final List widgets; - - /// Abstract class for organizer panel in the left. - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - final listEquals = const DeepCollectionEquality().equals; - - return other is ExpandableOrganizer && - other.name == name && - other.isExpanded == isExpanded && - listEquals(other.folders, folders) && - listEquals(other.widgets, widgets); - } - - @override - int get hashCode => isExpanded.hashCode ^ folders.hashCode ^ widgets.hashCode; - - @override - String toString() { - return 'Expanded: $isExpanded, Name: $name, Folders: $folders, Widgets: $widgets'; - } -} diff --git a/packages/widgetbook/lib/src/models/organizers/organizer.dart b/packages/widgetbook/lib/src/models/organizers/organizer.dart deleted file mode 100644 index 3589cc0ca..000000000 --- a/packages/widgetbook/lib/src/models/organizers/organizer.dart +++ /dev/null @@ -1,22 +0,0 @@ -/// Organizer is an abstract model which helps to -/// structure Categories, WidgetElements and Stories in the folder tree. -abstract class Organizer { - Organizer(this.name); - - /// Used to display the name of the Folder or WidgetElement - final String name; - - /// Used for navigation and matching hot reloaded elements with existing - String get path { - var path = name.replaceAll(' ', '-').toLowerCase(); - var current = parent; - while (current?.parent != null) { - path = '${current!.name.replaceAll(' ', '-').toLowerCase()}${'/$path'}'; - current = current.parent; - } - return path; - } - - /// The Organizer hosting this element. - Organizer? parent; -} diff --git a/packages/widgetbook/lib/src/models/organizers/organizer_helper/folder_helper.dart b/packages/widgetbook/lib/src/models/organizers/organizer_helper/folder_helper.dart deleted file mode 100644 index 72d34a94b..000000000 --- a/packages/widgetbook/lib/src/models/organizers/organizer_helper/folder_helper.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:widgetbook/widgetbook.dart'; - -/// Helper to navigate the folder tree. -class FolderHelper { - static List getAllFoldersFromCategories( - List categories, - ) { - final folders = []; - for (final category in categories) { - folders.addAll( - getAllFoldersFromCategory(category), - ); - } - return folders; - } - - static List getAllFoldersFromCategory( - WidgetbookCategory category, - ) { - return getAllFoldersFromFolders(category.folders); - } - - static List getAllFoldersFromFolders( - List folders, - ) { - final folderList = []; - for (final folder in folders) { - folderList.addAll( - getAllFoldersFromFolder(folder), - ); - } - return folderList; - } - - static List getAllFoldersFromFolder( - WidgetbookFolder folder, - ) { - final folderList = List.from( - [folder], - )..addAll( - getAllFoldersFromFolders(folder.folders), - ); - return folderList; - } -} diff --git a/packages/widgetbook/lib/src/models/organizers/organizer_helper/organizer_helper.dart b/packages/widgetbook/lib/src/models/organizers/organizer_helper/organizer_helper.dart deleted file mode 100644 index ac82b41d5..000000000 --- a/packages/widgetbook/lib/src/models/organizers/organizer_helper/organizer_helper.dart +++ /dev/null @@ -1,3 +0,0 @@ -export './folder_helper.dart'; -export './story_helper.dart'; -export './widget_helper.dart'; diff --git a/packages/widgetbook/lib/src/models/organizers/organizer_helper/story_helper.dart b/packages/widgetbook/lib/src/models/organizers/organizer_helper/story_helper.dart deleted file mode 100644 index f89daee2e..000000000 --- a/packages/widgetbook/lib/src/models/organizers/organizer_helper/story_helper.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:widgetbook/src/models/organizers/organizer_helper/organizer_helper.dart'; -import 'package:widgetbook/src/models/organizers/organizers.dart'; - -/// Helper to obtain all Stories in the navigation tree. -class StoryHelper { - static List getAllStoriesFromCategories( - List categories, - ) { - final widgets = WidgetHelper.getAllWidgetElementsFromCategories( - categories, - ); - - final stories = List.empty(growable: true); - for (final widget in widgets) { - stories.addAll(widget.useCases); - } - - return stories; - } -} diff --git a/packages/widgetbook/lib/src/models/organizers/organizer_helper/widget_helper.dart b/packages/widgetbook/lib/src/models/organizers/organizer_helper/widget_helper.dart deleted file mode 100644 index 7684e470c..000000000 --- a/packages/widgetbook/lib/src/models/organizers/organizer_helper/widget_helper.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:widgetbook/widgetbook.dart'; - -/// helper to obtain all WidgetElements in the navigation tree. -class WidgetHelper { - static List getAllWidgetElementsFromCategories( - List categories, - ) { - final widgets = []; - for (final category in categories) { - widgets.addAll( - getAllWidgetElementsFromCategory(category), - ); - } - return widgets; - } - - static List getAllWidgetElementsFromCategory( - WidgetbookCategory category, - ) { - final widgetList = List.from( - category.widgets, - )..addAll( - getAllWidgetElementsFromFolders(category.folders), - ); - return widgetList; - } - - static List getAllWidgetElementsFromFolders( - List folders, - ) { - final widgetList = []; - for (final folder in folders) { - widgetList.addAll( - getAllWidgetElementsFromFolder(folder), - ); - } - return widgetList; - } - - static List getAllWidgetElementsFromFolder( - WidgetbookFolder folder, - ) { - final widgetList = List.from(folder.widgets) - ..addAll( - getAllWidgetElementsFromFolders(folder.folders), - ); - return widgetList; - } -} diff --git a/packages/widgetbook/lib/src/models/organizers/organizers.dart b/packages/widgetbook/lib/src/models/organizers/organizers.dart deleted file mode 100644 index ad5c57989..000000000 --- a/packages/widgetbook/lib/src/models/organizers/organizers.dart +++ /dev/null @@ -1,6 +0,0 @@ -export 'expandable_organizer.dart'; -export 'organizer.dart'; -export 'widgetbook_category.dart'; -export 'widgetbook_folder.dart'; -export 'widgetbook_use_case.dart'; -export 'widgetbook_widget.dart'; diff --git a/packages/widgetbook/lib/src/models/organizers/widgetbook_category.dart b/packages/widgetbook/lib/src/models/organizers/widgetbook_category.dart deleted file mode 100644 index ced260e1b..000000000 --- a/packages/widgetbook/lib/src/models/organizers/widgetbook_category.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:widgetbook/src/models/organizers/expandable_organizer.dart'; -import 'package:widgetbook/src/models/organizers/widgetbook_folder.dart'; -import 'package:widgetbook/src/models/organizers/widgetbook_widget.dart'; - -/// Categories help to organize WidgetElements and Stories into different areas. -// TODO would be great if this uses freezed -class WidgetbookCategory extends ExpandableOrganizer { - WidgetbookCategory({ - required String name, - List? folders, - List? widgets, - bool isExpanded = true, - }) : super( - name: name, - folders: folders, - widgets: widgets, - isExpanded: isExpanded, - ) { - for (final ExpandableOrganizer organizer in this.folders) { - organizer.parent = this; - } - for (final ExpandableOrganizer organizer in this.widgets) { - organizer.parent = this; - } - } - - @override - String toString() { - return 'isExpanded: $isExpanded, folders: $folders, widgets: $widgets'; - } -} diff --git a/packages/widgetbook/lib/src/models/organizers/widgetbook_folder.dart b/packages/widgetbook/lib/src/models/organizers/widgetbook_folder.dart deleted file mode 100644 index 8fe018ff2..000000000 --- a/packages/widgetbook/lib/src/models/organizers/widgetbook_folder.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:widgetbook/src/models/organizers/organizers.dart'; - -/// A folder in the folder tree. -class WidgetbookFolder extends ExpandableOrganizer { - WidgetbookFolder({ - required String name, - List? folders, - List? widgets, - bool isExpanded = false, - }) : super( - name: name, - folders: folders, - widgets: widgets, - isExpanded: isExpanded, - ) { - for (final ExpandableOrganizer organizer in this.folders) { - organizer.parent = this; - } - for (final organizer in this.widgets) { - organizer.parent = this; - } - } - @override - String toString() { - return 'name: $name, expanded: $isExpanded, folders: $folders, widgets: $widgets'; - } -} diff --git a/packages/widgetbook/lib/src/models/organizers/widgetbook_widget.dart b/packages/widgetbook/lib/src/models/organizers/widgetbook_widget.dart deleted file mode 100644 index f56cad7f4..000000000 --- a/packages/widgetbook/lib/src/models/organizers/widgetbook_widget.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:widgetbook/src/models/organizers/organizers.dart'; - -/// -class WidgetbookComponent extends ExpandableOrganizer { - WidgetbookComponent({ - required String name, - required this.useCases, - bool isExpanded = false, - }) : super( - name: name, - isExpanded: isExpanded, - ) { - for (final state in useCases) { - state.parent = this; - } - } - - /// Use cases define specific configurations of a widget which allows - /// rendering the widget in isolation - final List useCases; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - final listEquals = const DeepCollectionEquality().equals; - - return other is WidgetbookComponent && listEquals(other.useCases, useCases); - } - - @override - int get hashCode => useCases.hashCode; - - @override - String toString() { - return 'name: $name, expanded: $isExpanded'; - } -} diff --git a/packages/widgetbook/lib/src/navigation/helpers/widgetbook_use_case_helper.dart b/packages/widgetbook/lib/src/navigation/helpers/widgetbook_use_case_helper.dart new file mode 100644 index 000000000..ce1f19b29 --- /dev/null +++ b/packages/widgetbook/lib/src/navigation/helpers/widgetbook_use_case_helper.dart @@ -0,0 +1,21 @@ +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_core/widgetbook_core.dart'; + +class WidgetbookUseCaseHelper { + static List fromNodes( + List nodes, + ) { + final useCases = []; + for (final node in nodes) { + if (node is WidgetbookComponent) { + useCases.addAll(node.useCases); + } else { + final children = node.children; + if (children is List && children.isNotEmpty) { + useCases.addAll(fromNodes(children)); + } + } + } + return useCases; + } +} diff --git a/packages/widgetbook/lib/src/navigation/models/models.dart b/packages/widgetbook/lib/src/navigation/models/models.dart new file mode 100644 index 000000000..98e9db6c2 --- /dev/null +++ b/packages/widgetbook/lib/src/navigation/models/models.dart @@ -0,0 +1,5 @@ +export './widgetbook_category.dart'; +export './widgetbook_component.dart'; +export './widgetbook_folder.dart'; +export './widgetbook_package.dart'; +export './widgetbook_use_case.dart'; diff --git a/packages/widgetbook/lib/src/navigation/models/widgetbook_category.dart b/packages/widgetbook/lib/src/navigation/models/widgetbook_category.dart new file mode 100644 index 000000000..d584be45a --- /dev/null +++ b/packages/widgetbook/lib/src/navigation/models/widgetbook_category.dart @@ -0,0 +1,12 @@ +import 'package:widgetbook_core/widgetbook_core.dart'; + +class WidgetbookCategory extends MultiChildNavigationNodeData { + WidgetbookCategory({ + required super.name, + required List children, + super.isInitiallyExpanded = true, + }) : super( + children: children, + type: NavigationNodeType.category, + ); +} diff --git a/packages/widgetbook/lib/src/navigation/models/widgetbook_component.dart b/packages/widgetbook/lib/src/navigation/models/widgetbook_component.dart new file mode 100644 index 000000000..9126a8676 --- /dev/null +++ b/packages/widgetbook/lib/src/navigation/models/widgetbook_component.dart @@ -0,0 +1,15 @@ +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_core/widgetbook_core.dart'; + +class WidgetbookComponent extends MultiChildNavigationNodeData { + WidgetbookComponent({ + required super.name, + required this.useCases, + super.isInitiallyExpanded = true, + }) : super( + children: useCases, + type: NavigationNodeType.component, + ); + + final List useCases; +} diff --git a/packages/widgetbook/lib/src/navigation/models/widgetbook_folder.dart b/packages/widgetbook/lib/src/navigation/models/widgetbook_folder.dart new file mode 100644 index 000000000..8f707abc8 --- /dev/null +++ b/packages/widgetbook/lib/src/navigation/models/widgetbook_folder.dart @@ -0,0 +1,12 @@ +import 'package:widgetbook_core/widgetbook_core.dart'; + +class WidgetbookFolder extends MultiChildNavigationNodeData { + WidgetbookFolder({ + required super.name, + required List children, + super.isInitiallyExpanded = true, + }) : super( + children: children, + type: NavigationNodeType.folder, + ); +} diff --git a/packages/widgetbook/lib/src/navigation/models/widgetbook_package.dart b/packages/widgetbook/lib/src/navigation/models/widgetbook_package.dart new file mode 100644 index 000000000..96c09e558 --- /dev/null +++ b/packages/widgetbook/lib/src/navigation/models/widgetbook_package.dart @@ -0,0 +1,12 @@ +import 'package:widgetbook_core/widgetbook_core.dart'; + +class WidgetbookPackage extends MultiChildNavigationNodeData { + WidgetbookPackage({ + required super.name, + required List children, + super.isInitiallyExpanded = true, + }) : super( + children: children, + type: NavigationNodeType.package, + ); +} diff --git a/packages/widgetbook/lib/src/models/organizers/widgetbook_use_case.dart b/packages/widgetbook/lib/src/navigation/models/widgetbook_use_case.dart similarity index 55% rename from packages/widgetbook/lib/src/models/organizers/widgetbook_use_case.dart rename to packages/widgetbook/lib/src/navigation/models/widgetbook_use_case.dart index e8c69faea..65aa4eaea 100644 --- a/packages/widgetbook/lib/src/models/organizers/widgetbook_use_case.dart +++ b/packages/widgetbook/lib/src/navigation/models/widgetbook_use_case.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:widgetbook/src/models/model.dart'; -import 'package:widgetbook/src/models/organizers/organizer.dart'; +import 'package:widgetbook_core/widgetbook_core.dart'; /// UseCases represent a specific configuration of a widget and can be used /// to check edge cases of a Widget. -class WidgetbookUseCase extends Organizer implements Model { - WidgetbookUseCase({required String name, required this.builder}) - : super(name); +class WidgetbookUseCase extends LeafNavigationNodeData implements Model { + WidgetbookUseCase({ + required super.name, + required this.builder, + }) : super(type: NavigationNodeType.useCase); factory WidgetbookUseCase.center({ required String name, @@ -31,19 +32,30 @@ class WidgetbookUseCase extends Organizer implements Model { final Widget Function(BuildContext) builder; - @override - String get id => path; - @override bool operator ==(Object other) { if (identical(this, other)) return true; - return other is WidgetbookUseCase && other.builder == builder; + return other is WidgetbookUseCase && + other.id == id && + other.name == name && + other.builder == builder && + other.isInitiallyExpanded == other.isInitiallyExpanded; } @override int get hashCode => builder.hashCode; @override - String toString() => 'WidgetbookUseCase(name: $name, builder: $builder)'; + String toString() { + return 'WidgetbookUseCase(' + 'id: $id, ' + 'name: $name, ' + 'builder: $builder, ' + 'isInitiallyExpanded: $isInitiallyExpanded' + ')'; + } + + @override + String get id => path; } diff --git a/packages/widgetbook/lib/src/navigation/navigation.dart b/packages/widgetbook/lib/src/navigation/navigation.dart new file mode 100644 index 000000000..4c48f181d --- /dev/null +++ b/packages/widgetbook/lib/src/navigation/navigation.dart @@ -0,0 +1,3 @@ +export './models/models.dart'; +export './providers/providers.dart'; +export './widgets/widgets.dart'; diff --git a/packages/widgetbook/lib/src/navigation/navigation_panel.dart b/packages/widgetbook/lib/src/navigation/navigation_panel.dart deleted file mode 100644 index ef33f062b..000000000 --- a/packages/widgetbook/lib/src/navigation/navigation_panel.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:widgetbook/src/app_info/app_info.dart'; -import 'package:widgetbook/src/constants/radii.dart'; -import 'package:widgetbook/src/models/organizers/organizers.dart'; -import 'package:widgetbook/src/widgets/expanders/expander_row.dart'; -import 'package:widgetbook/src/widgets/header.dart'; -import 'package:widgetbook/src/widgets/search_bar.dart'; -import 'package:widgetbook/src/widgets/tiles/category_tile.dart'; - -class NavigationPanel extends StatefulWidget { - const NavigationPanel({ - super.key, - required this.appInfo, - required this.categories, - }); - - final AppInfo appInfo; - final List categories; - - @override - _NavigationPanelState createState() => _NavigationPanelState(); -} - -class _NavigationPanelState extends State { - final ScrollController controller = ScrollController(); - WidgetbookUseCase? selectedComponent; - - final TextEditingController search = TextEditingController(); - String query = ''; - - Widget _buildCategory(BuildContext context, int i) { - final item = widget.categories[i]; - return CategoryTile(category: item); - } - - @override - Widget build(BuildContext context) { - return Container( - constraints: const BoxConstraints(minWidth: 50, maxWidth: 400), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Header( - appInfo: widget.appInfo, - ), - const SizedBox( - height: 16, - ), - const SearchBar(), - const SizedBox( - height: 16, - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - ExpanderRow.large( - organizers: widget.categories, - ), - ], - ), - Expanded( - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: Radii.defaultRadius, - ), - padding: const EdgeInsets.all(16), - child: Builder( - builder: (context) { - return ListView.separated( - controller: controller, - physics: const AlwaysScrollableScrollPhysics(), - itemCount: widget.categories.length, - itemBuilder: _buildCategory, - padding: const EdgeInsets.only(bottom: 8), - separatorBuilder: (context, index) => - const SizedBox(height: 8), - ); - }, - ), - ), - ), - ], - ), - ); - } -} diff --git a/packages/widgetbook/lib/src/navigation/organizer_provider.dart b/packages/widgetbook/lib/src/navigation/organizer_provider.dart deleted file mode 100644 index ed27e39b7..000000000 --- a/packages/widgetbook/lib/src/navigation/organizer_provider.dart +++ /dev/null @@ -1,132 +0,0 @@ -import 'package:widgetbook/src/models/models.dart'; -import 'package:widgetbook/src/models/organizers/organizer_helper/organizer_helper.dart'; -import 'package:widgetbook/src/navigation/organizer_state.dart'; -import 'package:widgetbook/src/repositories/story_repository.dart'; -import 'package:widgetbook/src/services/filter_service.dart'; -import 'package:widgetbook/src/state_change_notifier.dart'; - -class OrganizerProvider extends StateChangeNotifier { - OrganizerProvider({ - required OrganizerState state, - required this.storyRepository, - this.filterService = const FilterService(), - }) : super(state: state); - - final StoryRepository storyRepository; - final FilterService filterService; - - void openStory(WidgetbookUseCase? story) { - if (story == null) { - return; - } - var currentOrganizer = story.parent as ExpandableOrganizer?; - while (currentOrganizer != null) { - currentOrganizer.isExpanded = true; - currentOrganizer = currentOrganizer.parent as ExpandableOrganizer?; - } - } - - void _updateFolders(List categories) { - final oldFolders = FolderHelper.getAllFoldersFromCategories( - state.allCategories, - ); - final newFolders = FolderHelper.getAllFoldersFromCategories( - categories, - ); - final oldFolderMap = { - for (var e in oldFolders) e.path: e, - }; - - for (final folder in newFolders) { - final path = folder.path; - if (oldFolderMap.containsKey(path)) { - folder.isExpanded = oldFolderMap[path]!.isExpanded; - } - } - } - - void _updateWidgets(List categories) { - final oldWidgets = WidgetHelper.getAllWidgetElementsFromCategories( - state.allCategories, - ); - final newWidgets = WidgetHelper.getAllWidgetElementsFromCategories( - categories, - ); - final oldFolderMap = { - for (var e in oldWidgets) e.path: e, - }; - - for (final widget in newWidgets) { - final path = widget.path; - if (oldFolderMap.containsKey(path)) { - widget.isExpanded = oldFolderMap[path]!.isExpanded; - } - } - } - - void hotReload(List categories) { - _updateFolders(categories); - _updateWidgets(categories); - state = OrganizerState.unfiltered(categories: categories); - - final stories = StoryHelper.getAllStoriesFromCategories(categories); - storyRepository - ..deleteAll() - ..addAll(stories); - } - - void resetFilter() { - state = OrganizerState.unfiltered( - categories: state.allCategories, - ); - } - - void filter(String searchTerm) { - final categories = filterService.filter( - searchTerm, - state.allCategories, - ); - - state = OrganizerState( - allCategories: state.allCategories, - filteredCategories: categories, - searchTerm: searchTerm, - ); - } - - void toggleExpander(ExpandableOrganizer organizer) { - // Since this is modifying the object directly instead of modifying via - // copyWith, we need to call notifyListeners and are not emmitting a new - // state object. The state will be adjusted by adjusted the object. - organizer.isExpanded = !organizer.isExpanded; - notifyListeners(); - } - - /// Recursively set the expanded field for an organizer and it's nested widgets. - /// Does not emit after toggling. - void _setExpandedRecursive(ExpandableOrganizer organizer, bool value) { - organizer.isExpanded = value; - for (final folder in organizer.folders) { - _setExpandedRecursive(folder, value); - } - for (final folder in organizer.widgets) { - _setExpandedRecursive(folder, value); - } - } - - /// Set isExpanded an [ExpandableOrganizer] and its nested organizer to - /// the `expanded` - void setExpandedRecursive( - List organizers, { - required bool expanded, - }) { - // Since this is modifying the object directly instead of modifying via - // copyWith, we need to call notifyListeners and are not emmitting a new - // state object. The state will be adjusted by adjusted the object. - for (final e in organizers) { - _setExpandedRecursive(e, expanded); - } - - notifyListeners(); - } -} diff --git a/packages/widgetbook/lib/src/navigation/organizer_state.dart b/packages/widgetbook/lib/src/navigation/organizer_state.dart deleted file mode 100644 index 4bcd4f9c5..000000000 --- a/packages/widgetbook/lib/src/navigation/organizer_state.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:widgetbook/src/models/models.dart'; - -part 'organizer_state.freezed.dart'; - -@freezed -class OrganizerState with _$OrganizerState { - factory OrganizerState({ - required List allCategories, - required List filteredCategories, - required String searchTerm, - }) = _OrganizerState; - - factory OrganizerState.unfiltered({ - required List categories, - }) { - return OrganizerState( - allCategories: categories, - filteredCategories: categories, - searchTerm: '', - ); - } -} diff --git a/packages/widgetbook/lib/src/navigation/organizer_state.freezed.dart b/packages/widgetbook/lib/src/navigation/organizer_state.freezed.dart deleted file mode 100644 index 3a9869524..000000000 --- a/packages/widgetbook/lib/src/navigation/organizer_state.freezed.dart +++ /dev/null @@ -1,194 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target - -part of 'organizer_state.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -/// @nodoc -mixin _$OrganizerState { - List get allCategories => - throw _privateConstructorUsedError; - List get filteredCategories => - throw _privateConstructorUsedError; - String get searchTerm => throw _privateConstructorUsedError; - - @JsonKey(ignore: true) - $OrganizerStateCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $OrganizerStateCopyWith<$Res> { - factory $OrganizerStateCopyWith( - OrganizerState value, $Res Function(OrganizerState) then) = - _$OrganizerStateCopyWithImpl<$Res>; - $Res call( - {List allCategories, - List filteredCategories, - String searchTerm}); -} - -/// @nodoc -class _$OrganizerStateCopyWithImpl<$Res> - implements $OrganizerStateCopyWith<$Res> { - _$OrganizerStateCopyWithImpl(this._value, this._then); - - final OrganizerState _value; - // ignore: unused_field - final $Res Function(OrganizerState) _then; - - @override - $Res call({ - Object? allCategories = freezed, - Object? filteredCategories = freezed, - Object? searchTerm = freezed, - }) { - return _then(_value.copyWith( - allCategories: allCategories == freezed - ? _value.allCategories - : allCategories // ignore: cast_nullable_to_non_nullable - as List, - filteredCategories: filteredCategories == freezed - ? _value.filteredCategories - : filteredCategories // ignore: cast_nullable_to_non_nullable - as List, - searchTerm: searchTerm == freezed - ? _value.searchTerm - : searchTerm // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc -abstract class _$$_OrganizerStateCopyWith<$Res> - implements $OrganizerStateCopyWith<$Res> { - factory _$$_OrganizerStateCopyWith( - _$_OrganizerState value, $Res Function(_$_OrganizerState) then) = - __$$_OrganizerStateCopyWithImpl<$Res>; - @override - $Res call( - {List allCategories, - List filteredCategories, - String searchTerm}); -} - -/// @nodoc -class __$$_OrganizerStateCopyWithImpl<$Res> - extends _$OrganizerStateCopyWithImpl<$Res> - implements _$$_OrganizerStateCopyWith<$Res> { - __$$_OrganizerStateCopyWithImpl( - _$_OrganizerState _value, $Res Function(_$_OrganizerState) _then) - : super(_value, (v) => _then(v as _$_OrganizerState)); - - @override - _$_OrganizerState get _value => super._value as _$_OrganizerState; - - @override - $Res call({ - Object? allCategories = freezed, - Object? filteredCategories = freezed, - Object? searchTerm = freezed, - }) { - return _then(_$_OrganizerState( - allCategories: allCategories == freezed - ? _value._allCategories - : allCategories // ignore: cast_nullable_to_non_nullable - as List, - filteredCategories: filteredCategories == freezed - ? _value._filteredCategories - : filteredCategories // ignore: cast_nullable_to_non_nullable - as List, - searchTerm: searchTerm == freezed - ? _value.searchTerm - : searchTerm // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc - -class _$_OrganizerState implements _OrganizerState { - _$_OrganizerState( - {required final List allCategories, - required final List filteredCategories, - required this.searchTerm}) - : _allCategories = allCategories, - _filteredCategories = filteredCategories; - - final List _allCategories; - @override - List get allCategories { - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_allCategories); - } - - final List _filteredCategories; - @override - List get filteredCategories { - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_filteredCategories); - } - - @override - final String searchTerm; - - @override - String toString() { - return 'OrganizerState(allCategories: $allCategories, filteredCategories: $filteredCategories, searchTerm: $searchTerm)'; - } - - @override - bool operator ==(dynamic other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$_OrganizerState && - const DeepCollectionEquality() - .equals(other._allCategories, _allCategories) && - const DeepCollectionEquality() - .equals(other._filteredCategories, _filteredCategories) && - const DeepCollectionEquality() - .equals(other.searchTerm, searchTerm)); - } - - @override - int get hashCode => Object.hash( - runtimeType, - const DeepCollectionEquality().hash(_allCategories), - const DeepCollectionEquality().hash(_filteredCategories), - const DeepCollectionEquality().hash(searchTerm)); - - @JsonKey(ignore: true) - @override - _$$_OrganizerStateCopyWith<_$_OrganizerState> get copyWith => - __$$_OrganizerStateCopyWithImpl<_$_OrganizerState>(this, _$identity); -} - -abstract class _OrganizerState implements OrganizerState { - factory _OrganizerState( - {required final List allCategories, - required final List filteredCategories, - required final String searchTerm}) = _$_OrganizerState; - - @override - List get allCategories; - @override - List get filteredCategories; - @override - String get searchTerm; - @override - @JsonKey(ignore: true) - _$$_OrganizerStateCopyWith<_$_OrganizerState> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/packages/widgetbook/lib/src/navigation/providers/navigation_tree_provider.dart b/packages/widgetbook/lib/src/navigation/providers/navigation_tree_provider.dart new file mode 100644 index 000000000..1e360e249 --- /dev/null +++ b/packages/widgetbook/lib/src/navigation/providers/navigation_tree_provider.dart @@ -0,0 +1,49 @@ +import 'package:widgetbook/src/navigation/helpers/widgetbook_use_case_helper.dart'; +import 'package:widgetbook/src/repositories/story_repository.dart'; +import 'package:widgetbook/src/state_change_notifier.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_core/widgetbook_core.dart'; + +class NavigationTreeProvider extends StateChangeNotifier { + NavigationTreeProvider({ + required super.state, + required this.storyRepository, + }); + + final StoryRepository storyRepository; + + void hotReload(List nodes) { + state = NavigationTreeState.unfiltered(nodes: nodes); + final useCases = WidgetbookUseCaseHelper.fromNodes(nodes); + storyRepository + ..deleteAll() + ..addAll(useCases); + } + + void filter(String searchQuery) { + final filteredNodes = filterNodes(searchQuery); + + state = state.copyWith( + filteredNodes: filteredNodes, + ); + } + + void resetFilter() { + state = state.copyWith( + filteredNodes: state.nodes, + ); + } + + // Todo: use `FilterService` instead + List filterNodes(String searchQuery) { + final nodes = state.nodes; + final filteredNodes = []; + for (final node in nodes) { + final matchedNode = node.filterNode(searchQuery); + if (matchedNode != null) { + filteredNodes.add(matchedNode); + } + } + return filteredNodes; + } +} diff --git a/packages/widgetbook/lib/src/navigation/providers/navigation_tree_state.dart b/packages/widgetbook/lib/src/navigation/providers/navigation_tree_state.dart new file mode 100644 index 000000000..3486db848 --- /dev/null +++ b/packages/widgetbook/lib/src/navigation/providers/navigation_tree_state.dart @@ -0,0 +1,23 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:widgetbook_core/widgetbook_core.dart'; + +part 'navigation_tree_state.freezed.dart'; + +@freezed +class NavigationTreeState with _$NavigationTreeState { + const factory NavigationTreeState({ + @Default([]) + List nodes, + @Default([]) + List filteredNodes, + }) = _NavigationTreeState; + + factory NavigationTreeState.unfiltered({ + required List nodes, + }) { + return NavigationTreeState( + nodes: nodes, + filteredNodes: nodes, + ); + } +} diff --git a/packages/widgetbook/lib/src/navigation/providers/navigation_tree_state.freezed.dart b/packages/widgetbook/lib/src/navigation/providers/navigation_tree_state.freezed.dart new file mode 100644 index 000000000..dd8d0229d --- /dev/null +++ b/packages/widgetbook/lib/src/navigation/providers/navigation_tree_state.freezed.dart @@ -0,0 +1,176 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target + +part of 'navigation_tree_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$NavigationTreeState { + List get nodes => + throw _privateConstructorUsedError; + List get filteredNodes => + throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $NavigationTreeStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $NavigationTreeStateCopyWith<$Res> { + factory $NavigationTreeStateCopyWith( + NavigationTreeState value, $Res Function(NavigationTreeState) then) = + _$NavigationTreeStateCopyWithImpl<$Res>; + $Res call( + {List nodes, + List filteredNodes}); +} + +/// @nodoc +class _$NavigationTreeStateCopyWithImpl<$Res> + implements $NavigationTreeStateCopyWith<$Res> { + _$NavigationTreeStateCopyWithImpl(this._value, this._then); + + final NavigationTreeState _value; + // ignore: unused_field + final $Res Function(NavigationTreeState) _then; + + @override + $Res call({ + Object? nodes = freezed, + Object? filteredNodes = freezed, + }) { + return _then(_value.copyWith( + nodes: nodes == freezed + ? _value.nodes + : nodes // ignore: cast_nullable_to_non_nullable + as List, + filteredNodes: filteredNodes == freezed + ? _value.filteredNodes + : filteredNodes // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +abstract class _$$_NavigationTreeStateCopyWith<$Res> + implements $NavigationTreeStateCopyWith<$Res> { + factory _$$_NavigationTreeStateCopyWith(_$_NavigationTreeState value, + $Res Function(_$_NavigationTreeState) then) = + __$$_NavigationTreeStateCopyWithImpl<$Res>; + @override + $Res call( + {List nodes, + List filteredNodes}); +} + +/// @nodoc +class __$$_NavigationTreeStateCopyWithImpl<$Res> + extends _$NavigationTreeStateCopyWithImpl<$Res> + implements _$$_NavigationTreeStateCopyWith<$Res> { + __$$_NavigationTreeStateCopyWithImpl(_$_NavigationTreeState _value, + $Res Function(_$_NavigationTreeState) _then) + : super(_value, (v) => _then(v as _$_NavigationTreeState)); + + @override + _$_NavigationTreeState get _value => super._value as _$_NavigationTreeState; + + @override + $Res call({ + Object? nodes = freezed, + Object? filteredNodes = freezed, + }) { + return _then(_$_NavigationTreeState( + nodes: nodes == freezed + ? _value._nodes + : nodes // ignore: cast_nullable_to_non_nullable + as List, + filteredNodes: filteredNodes == freezed + ? _value._filteredNodes + : filteredNodes // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +class _$_NavigationTreeState implements _NavigationTreeState { + const _$_NavigationTreeState( + {final List nodes = + const [], + final List filteredNodes = + const []}) + : _nodes = nodes, + _filteredNodes = filteredNodes; + + final List _nodes; + @override + @JsonKey() + List get nodes { + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_nodes); + } + + final List _filteredNodes; + @override + @JsonKey() + List get filteredNodes { + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_filteredNodes); + } + + @override + String toString() { + return 'NavigationTreeState(nodes: $nodes, filteredNodes: $filteredNodes)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_NavigationTreeState && + const DeepCollectionEquality().equals(other._nodes, _nodes) && + const DeepCollectionEquality() + .equals(other._filteredNodes, _filteredNodes)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_nodes), + const DeepCollectionEquality().hash(_filteredNodes)); + + @JsonKey(ignore: true) + @override + _$$_NavigationTreeStateCopyWith<_$_NavigationTreeState> get copyWith => + __$$_NavigationTreeStateCopyWithImpl<_$_NavigationTreeState>( + this, _$identity); +} + +abstract class _NavigationTreeState implements NavigationTreeState { + const factory _NavigationTreeState( + {final List nodes, + final List filteredNodes}) = + _$_NavigationTreeState; + + @override + List get nodes; + @override + List get filteredNodes; + @override + @JsonKey(ignore: true) + _$$_NavigationTreeStateCopyWith<_$_NavigationTreeState> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/widgetbook/lib/src/navigation/preview_provider.dart b/packages/widgetbook/lib/src/navigation/providers/preview_provider.dart similarity index 92% rename from packages/widgetbook/lib/src/navigation/preview_provider.dart rename to packages/widgetbook/lib/src/navigation/providers/preview_provider.dart index 1ced00e0b..6532b2064 100644 --- a/packages/widgetbook/lib/src/navigation/preview_provider.dart +++ b/packages/widgetbook/lib/src/navigation/providers/preview_provider.dart @@ -1,8 +1,8 @@ -import 'package:widgetbook/src/models/models.dart'; -import 'package:widgetbook/src/navigation/preview_state.dart'; +import 'package:widgetbook/src/navigation/providers/preview_state.dart'; import 'package:widgetbook/src/repositories/selected_story_repository.dart'; import 'package:widgetbook/src/repositories/story_repository.dart'; import 'package:widgetbook/src/state_change_notifier.dart'; +import 'package:widgetbook/widgetbook.dart'; class PreviewProvider extends StateChangeNotifier { PreviewProvider({ diff --git a/packages/widgetbook/lib/src/navigation/preview_state.dart b/packages/widgetbook/lib/src/navigation/providers/preview_state.dart similarity index 88% rename from packages/widgetbook/lib/src/navigation/preview_state.dart rename to packages/widgetbook/lib/src/navigation/providers/preview_state.dart index cc9a3b620..23872d807 100644 --- a/packages/widgetbook/lib/src/navigation/preview_state.dart +++ b/packages/widgetbook/lib/src/navigation/providers/preview_state.dart @@ -1,5 +1,5 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:widgetbook/src/models/models.dart'; +import 'package:widgetbook/widgetbook.dart'; part 'preview_state.freezed.dart'; diff --git a/packages/widgetbook/lib/src/navigation/preview_state.freezed.dart b/packages/widgetbook/lib/src/navigation/providers/preview_state.freezed.dart similarity index 100% rename from packages/widgetbook/lib/src/navigation/preview_state.freezed.dart rename to packages/widgetbook/lib/src/navigation/providers/preview_state.freezed.dart diff --git a/packages/widgetbook/lib/src/navigation/providers/providers.dart b/packages/widgetbook/lib/src/navigation/providers/providers.dart new file mode 100644 index 000000000..5830e2364 --- /dev/null +++ b/packages/widgetbook/lib/src/navigation/providers/providers.dart @@ -0,0 +1,4 @@ +export './navigation_tree_provider.dart'; +export './navigation_tree_state.dart'; +export './preview_provider.dart'; +export './preview_state.dart'; diff --git a/packages/widgetbook/lib/src/navigation/router.dart b/packages/widgetbook/lib/src/navigation/router.dart index d89693db3..5f8831fae 100644 --- a/packages/widgetbook/lib/src/navigation/router.dart +++ b/packages/widgetbook/lib/src/navigation/router.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:widgetbook/src/addons/addon_provider.dart'; -import 'package:widgetbook/src/navigation/preview_provider.dart'; +import 'package:widgetbook/src/navigation/providers/preview_provider.dart'; import 'package:widgetbook/src/widgetbook_page.dart'; T? parseRouterData({ diff --git a/packages/widgetbook/lib/src/navigation/widgets/navigation_panel.dart b/packages/widgetbook/lib/src/navigation/widgets/navigation_panel.dart new file mode 100644 index 000000000..f475db476 --- /dev/null +++ b/packages/widgetbook/lib/src/navigation/widgets/navigation_panel.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:widgetbook/src/app_info/models/app_info.dart'; +import 'package:widgetbook/src/navigation/models/models.dart'; +import 'package:widgetbook/src/navigation/providers/navigation_tree_provider.dart'; +import 'package:widgetbook/src/navigation/providers/preview_provider.dart'; +import 'package:widgetbook/src/navigation/router.dart'; +import 'package:widgetbook_core/widgetbook_core.dart'; + +class NavigationPanel extends StatelessWidget { + const NavigationPanel({ + super.key, + required this.appInfo, + }); + + final AppInfo appInfo; + + @override + Widget build(BuildContext context) { + final navigationTree = context.watch().state; + + return Container( + constraints: const BoxConstraints(minWidth: 50, maxWidth: 400), + child: Card( + color: Theme.of(context).colorScheme.inversePrimary.withOpacity(0.05), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: SearchField( + onSearchPressed: () {}, + onSearchChanged: (String value) { + context.read().filter(value); + }, + onSearchCancelled: () { + context.read().resetFilter(); + }, + ), + ), + // const SearchBar(), + const SizedBox(height: 16), + Expanded( + child: NavigationTree( + nodes: navigationTree.filteredNodes, + selectedNode: + context.watch().state.selectedUseCase, + onNodeSelected: (data) { + if (data is WidgetbookUseCase) { + context.read().selectUseCase(data); + navigate(context); + } + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/widgetbook/lib/src/navigation/widgets/widgets.dart b/packages/widgetbook/lib/src/navigation/widgets/widgets.dart new file mode 100644 index 000000000..a35e44135 --- /dev/null +++ b/packages/widgetbook/lib/src/navigation/widgets/widgets.dart @@ -0,0 +1 @@ +export './navigation_panel.dart'; diff --git a/packages/widgetbook/lib/src/repositories/memory_repository.dart b/packages/widgetbook/lib/src/repositories/memory_repository.dart index 8ccf56410..5eea7c42a 100644 --- a/packages/widgetbook/lib/src/repositories/memory_repository.dart +++ b/packages/widgetbook/lib/src/repositories/memory_repository.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'dart:collection'; import 'package:meta/meta.dart'; -import 'package:widgetbook/src/models/model.dart'; import 'package:widgetbook/src/repositories/repository.dart'; +import 'package:widgetbook_core/widgetbook_core.dart'; class MemoryRepository extends Repository { MemoryRepository({Map? initialConfiguration}) diff --git a/packages/widgetbook/lib/src/repositories/repository.dart b/packages/widgetbook/lib/src/repositories/repository.dart index d12152d67..d7d52b9e1 100644 --- a/packages/widgetbook/lib/src/repositories/repository.dart +++ b/packages/widgetbook/lib/src/repositories/repository.dart @@ -1,4 +1,4 @@ -import 'package:widgetbook/src/models/model.dart'; +import 'package:widgetbook_core/widgetbook_core.dart'; abstract class Repository { // Item operations diff --git a/packages/widgetbook/lib/src/services/filter_service.dart b/packages/widgetbook/lib/src/services/filter_service.dart index ef03c0d05..ad3293aa3 100644 --- a/packages/widgetbook/lib/src/services/filter_service.dart +++ b/packages/widgetbook/lib/src/services/filter_service.dart @@ -1,93 +1,19 @@ -import 'package:widgetbook/src/models/models.dart'; +import 'package:widgetbook_core/widgetbook_core.dart'; class FilterService { const FilterService(); - List filter( - String searchTerm, - List categories, + List filterNodes( + String searchQuery, + List nodes, ) { - return _filterCategories( - RegExp( - searchTerm, - caseSensitive: false, - ), - categories, - ); - } - - List _filterCategories( - RegExp regExp, - List categories, - ) { - final matchingOrganizers = []; - for (final category in categories) { - final result = _filterOrganizer(regExp, category) as WidgetbookCategory?; - if (_isMatch(result)) { - matchingOrganizers.add(result!); - } - } - return matchingOrganizers; - } - - ExpandableOrganizer? _filterOrganizer( - RegExp regExp, - ExpandableOrganizer organizer, - ) { - if (organizer.name.contains(regExp)) { - return organizer; - } - - final matchingFolders = []; - for (final subOrganizer in organizer.folders) { - final result = _filterOrganizer(regExp, subOrganizer); - if (_isMatch(result)) { - matchingFolders.add(result! as WidgetbookFolder); + final filteredNodes = []; + for (final node in nodes) { + final matchedNode = node.filterNode(searchQuery); + if (matchedNode != null) { + filteredNodes.add(matchedNode); } } - - final matchingWidgets = []; - for (final subOrganizer in organizer.widgets) { - final result = _filterOrganizer(regExp, subOrganizer); - if (_isMatch(result)) { - matchingWidgets.add(result! as WidgetbookComponent); - } - } - - if (matchingFolders.isNotEmpty || matchingWidgets.isNotEmpty) { - return _createFilteredSubtree( - organizer, - matchingFolders, - matchingWidgets, - ); - } - - return null; - } - - ExpandableOrganizer _createFilteredSubtree( - ExpandableOrganizer organizer, - List folders, - List widgets, - ) { - if (organizer is WidgetbookCategory) { - return WidgetbookCategory( - name: organizer.name, - widgets: widgets, - folders: folders, - ); - } - - // otherwise it can only be a folder - return WidgetbookFolder( - name: organizer.name, - widgets: widgets, - folders: folders, - isExpanded: organizer.isExpanded, - ); - } - - bool _isMatch(ExpandableOrganizer? organizer) { - return organizer != null; + return filteredNodes; } } diff --git a/packages/widgetbook/lib/src/widgetbook.dart b/packages/widgetbook/lib/src/widgetbook.dart index 362f9245f..058362beb 100644 --- a/packages/widgetbook/lib/src/widgetbook.dart +++ b/packages/widgetbook/lib/src/widgetbook.dart @@ -4,18 +4,16 @@ import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:widgetbook/src/addons/addon.dart'; import 'package:widgetbook/src/addons/addon_provider.dart'; -import 'package:widgetbook/src/app_info/app_info.dart'; -import 'package:widgetbook/src/app_info/app_info_provider.dart'; +import 'package:widgetbook/src/app_info/models/app_info.dart'; +import 'package:widgetbook/src/app_info/providers/app_info_provider.dart'; import 'package:widgetbook/src/builder/builder.dart'; import 'package:widgetbook/src/knobs/knobs.dart'; -import 'package:widgetbook/src/models/organizers/organizers.dart'; -import 'package:widgetbook/src/navigation/organizer_provider.dart'; -import 'package:widgetbook/src/navigation/organizer_state.dart'; -import 'package:widgetbook/src/navigation/preview_provider.dart'; +import 'package:widgetbook/src/navigation/navigation.dart'; import 'package:widgetbook/src/navigation/router.dart'; import 'package:widgetbook/src/repositories/selected_story_repository.dart'; import 'package:widgetbook/src/repositories/story_repository.dart'; import 'package:widgetbook/src/utils/styles.dart'; +import 'package:widgetbook_core/widgetbook_core.dart'; import 'package:widgetbook_models/widgetbook_models.dart'; /// Describes the configuration for your [Widget] library. @@ -39,21 +37,22 @@ import 'package:widgetbook_models/widgetbook_models.dart'; class Widgetbook extends StatefulWidget { const Widgetbook({ super.key, - required this.categories, + this.children = const [], required this.appInfo, this.appBuilder = defaultAppBuilder, required this.addons, }) : assert( - categories.length > 0, - 'Please specify at least one $WidgetbookCategory.', + children.length > 0, + 'Please specify at least one $MultiChildNavigationNodeData.', ); final List addons; - /// Categories which host Folders and WidgetElements. + /// Children which host Packages, Folders, Categories, Components + /// and Use cases. /// This can be used to organize the structure of the Widgetbook on a large /// scale. - final List categories; + final List children; /// Information about the app that is catalogued in the Widgetbook. final AppInfo appInfo; @@ -62,7 +61,7 @@ class Widgetbook extends StatefulWidget { /// A [Widgetbook] which uses cupertino theming via [CupertinoThemeData]. static Widgetbook cupertino({ - required List categories, + required List children, required AppInfo appInfo, required List addons, AppBuilderFunction? appBuilder, @@ -70,7 +69,7 @@ class Widgetbook extends StatefulWidget { }) { return Widgetbook( key: key, - categories: categories, + children: children, appInfo: appInfo, addons: addons, appBuilder: appBuilder ?? cupertinoAppBuilder, @@ -79,7 +78,7 @@ class Widgetbook extends StatefulWidget { /// A [Widgetbook] which uses material theming via [ThemeData]. static Widgetbook material({ - required List categories, + required List children, required AppInfo appInfo, required List addons, AppBuilderFunction? appBuilder, @@ -87,7 +86,7 @@ class Widgetbook extends StatefulWidget { }) { return Widgetbook( key: key, - categories: categories, + children: children, appInfo: appInfo, addons: addons, appBuilder: appBuilder ?? materialAppBuilder, @@ -104,8 +103,8 @@ class _WidgetbookState extends State> { final SelectedStoryRepository selectedStoryRepository = SelectedStoryRepository(); + late NavigationTreeProvider navigationTreeProvider; late BuilderProvider builderProvider; - late OrganizerProvider organizerProvider; late PreviewProvider previewProvider; late AppInfoProvider appInfoProvider; late KnobsNotifier knobsNotifier; @@ -114,10 +113,14 @@ class _WidgetbookState extends State> { @override void initState() { builderProvider = BuilderProvider(appBuilder: widget.appBuilder); - organizerProvider = OrganizerProvider( - state: OrganizerState.unfiltered(categories: widget.categories), + + navigationTreeProvider = NavigationTreeProvider( + state: NavigationTreeState.unfiltered( + nodes: widget.children, + ), storyRepository: storyRepository, - )..hotReload(widget.categories); + )..hotReload(widget.children); + previewProvider = PreviewProvider( storyRepository: storyRepository, selectedStoryRepository: selectedStoryRepository, @@ -134,7 +137,7 @@ class _WidgetbookState extends State> { @override void didUpdateWidget(covariant Widgetbook oldWidget) { - organizerProvider.hotReload(widget.categories); + navigationTreeProvider.hotReload(widget.children); appInfoProvider.hotReload(widget.appInfo); builderProvider.hotReload(appBuilder: widget.appBuilder); super.didUpdateWidget(oldWidget); @@ -145,7 +148,7 @@ class _WidgetbookState extends State> { return MultiProvider( providers: [ ChangeNotifierProvider.value(value: knobsNotifier), - ChangeNotifierProvider.value(value: organizerProvider), + ChangeNotifierProvider.value(value: navigationTreeProvider), ChangeNotifierProvider.value(value: previewProvider), ChangeNotifierProvider.value(value: appInfoProvider), ChangeNotifierProvider.value(value: builderProvider), @@ -160,7 +163,7 @@ class _WidgetbookState extends State> { title: widget.appInfo.name, themeMode: ThemeMode.dark, debugShowCheckedModeBanner: false, - darkTheme: Styles.darkTheme, + darkTheme: Themes.dark, theme: Styles.lightTheme, ), ); diff --git a/packages/widgetbook/lib/src/widgetbook_page.dart b/packages/widgetbook/lib/src/widgetbook_page.dart index 318848ffd..d719477e8 100644 --- a/packages/widgetbook/lib/src/widgetbook_page.dart +++ b/packages/widgetbook/lib/src/widgetbook_page.dart @@ -2,9 +2,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:widgetbook/src/addons/addon_injector_widget.dart'; import 'package:widgetbook/src/addons/addon_provider.dart'; -import 'package:widgetbook/src/app_info/app_info_provider.dart'; -import 'package:widgetbook/src/navigation/navigation_panel.dart'; -import 'package:widgetbook/src/navigation/organizer_provider.dart'; +import 'package:widgetbook/src/app_info/providers/app_info_provider.dart'; +import 'package:widgetbook/src/navigation/widgets/navigation_panel.dart'; import 'package:widgetbook/src/settings_panel/settings_panel.dart'; import 'package:widgetbook/src/styled_widgets/styled_scaffold.dart'; import 'package:widgetbook/src/widgets/multi_split_view.dart'; @@ -31,17 +30,14 @@ class WidgetbookPage extends StatelessWidget { builder: (context) { final addons = context.watch().value; final appInfo = context.watch().state; - final state = context.watch().state; + return AddonInjectorWidget( addons: addons, routerData: routerData, child: TrippleSplitView( isLeftDisabled: disableNavigation, isRightDisabled: disableProperties, - leftChild: NavigationPanel( - appInfo: appInfo, - categories: state.filteredCategories, - ), + leftChild: NavigationPanel(appInfo: appInfo), centerChild: const Workbench(), rightChild: const SettingsPanel(), ), diff --git a/packages/widgetbook/lib/src/widgets/expanders/expand_button.dart b/packages/widgetbook/lib/src/widgets/expanders/expand_button.dart deleted file mode 100644 index 247b94651..000000000 --- a/packages/widgetbook/lib/src/widgets/expanders/expand_button.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:widgetbook/src/navigation/organizer_provider.dart'; -import 'package:widgetbook/widgetbook.dart'; - -class ExpandButton extends StatefulWidget { - const ExpandButton({ - Key? key, - required this.organizers, - required this.expandTo, - this.size = 17, - }) : super(key: key); - - final List organizers; - final bool expandTo; - final double? size; - - @override - _ExpandButtonState createState() => _ExpandButtonState(); -} - -class _ExpandButtonState extends State { - @override - Widget build(BuildContext context) { - IconData icon; - String tooltip; - if (widget.expandTo) { - icon = Icons.expand_more; - tooltip = 'Expand all'; - } else { - icon = Icons.expand_less; - tooltip = 'Collapse all'; - } - return Tooltip( - message: tooltip, - waitDuration: const Duration(milliseconds: 500), - child: InkWell( - child: Icon( - icon, - size: widget.size, - ), - onTap: () { - context.read().setExpandedRecursive( - widget.organizers, - expanded: widget.expandTo, - ); - }, - ), - ); - } -} diff --git a/packages/widgetbook/lib/src/widgets/expanders/expander_row.dart b/packages/widgetbook/lib/src/widgets/expanders/expander_row.dart deleted file mode 100644 index 78059166e..000000000 --- a/packages/widgetbook/lib/src/widgets/expanders/expander_row.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:widgetbook/src/models/organizers/organizers.dart'; - -import 'package:widgetbook/src/widgets/expanders/expand_button.dart'; - -class ExpanderRow extends StatelessWidget { - const ExpanderRow({ - Key? key, - this.size, - required this.organizers, - }) : super(key: key); - - factory ExpanderRow.large({ - Key? key, - required List organizers, - }) { - return ExpanderRow( - key: key, - size: 32, - organizers: organizers, - ); - } - - factory ExpanderRow.small({ - Key? key, - required List organizers, - }) { - return ExpanderRow( - key: key, - size: 16, - organizers: organizers, - ); - } - - final double? size; - final List organizers; - - @override - Widget build(BuildContext context) { - return Row( - children: [ - ExpandButton( - organizers: organizers, - expandTo: true, - size: size, - ), - ExpandButton( - organizers: organizers, - expandTo: false, - size: size, - ), - ], - ); - } -} diff --git a/packages/widgetbook/lib/src/widgets/header.dart b/packages/widgetbook/lib/src/widgets/header.dart index 7a1a5f753..df95f11b3 100644 --- a/packages/widgetbook/lib/src/widgets/header.dart +++ b/packages/widgetbook/lib/src/widgets/header.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:widgetbook/src/app_info/app_info.dart'; +import 'package:widgetbook/src/app_info/models/app_info.dart'; import 'package:widgetbook/src/constants/constants.dart'; class Header extends StatelessWidget { diff --git a/packages/widgetbook/lib/src/widgets/multi_split_view.dart b/packages/widgetbook/lib/src/widgets/multi_split_view.dart index a1dd11a2a..1370a22ec 100644 --- a/packages/widgetbook/lib/src/widgets/multi_split_view.dart +++ b/packages/widgetbook/lib/src/widgets/multi_split_view.dart @@ -12,8 +12,8 @@ enum DetectorSide { class TrippleSplitView extends StatefulWidget { const TrippleSplitView({ Key? key, - this.leftMinWidth = 300, - this.rightMinWidth = 400, + this.leftMinWidth = 350, + this.rightMinWidth = 350, required this.leftChild, required this.centerChild, required this.rightChild, diff --git a/packages/widgetbook/lib/src/widgets/search_bar.dart b/packages/widgetbook/lib/src/widgets/search_bar.dart deleted file mode 100644 index a7e095f56..000000000 --- a/packages/widgetbook/lib/src/widgets/search_bar.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:widgetbook/src/constants/radii.dart'; -import 'package:widgetbook/src/navigation/organizer_provider.dart'; -import 'package:widgetbook/src/utils/utils.dart'; - -class SearchBar extends StatefulWidget { - const SearchBar({ - Key? key, - }) : super(key: key); - - @override - _SearchBarState createState() => _SearchBarState(); -} - -class _SearchBarState extends State { - TextEditingController controller = TextEditingController(); - - @override - Widget build(BuildContext context) { - // TODO how to handle dark and light theme of the app? - final fillColor = ThemeMode.dark == ThemeMode.light - ? Styles.lightSurface - : Styles.darkSurface; - final onFillColor = ThemeMode.dark == ThemeMode.light - ? Styles.onLightSurface - : Styles.onDarkSurface; - - const border = OutlineInputBorder( - borderSide: BorderSide( - color: Colors.transparent, - ), - borderRadius: Radii.defaultRadius, - ); - - return TextField( - key: Key('$SearchBar.$TextField'), - controller: controller, - cursorWidth: 3, - cursorColor: onFillColor, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.search), - hintText: 'search', - suffixIcon: - controller.text.isNotEmpty ? _buildCancelSearchButton() : null, - filled: true, - fillColor: fillColor, - border: border, - enabledBorder: border, - focusedBorder: border, - ), - onChanged: (value) { - setState(() {}); - context.read().filter(value); - }, - ); - } - - Widget _buildCancelSearchButton() { - return IconButton( - key: Key('$SearchBar.CancelSearchButton'), - icon: const Icon(Icons.close), - splashRadius: 20, - onPressed: () { - setState( - () { - controller = TextEditingController(); - }, - ); - - context.read().resetFilter(); - }, - ); - } -} diff --git a/packages/widgetbook/lib/src/widgets/tiles/category_tile.dart b/packages/widgetbook/lib/src/widgets/tiles/category_tile.dart deleted file mode 100644 index f28e52a56..000000000 --- a/packages/widgetbook/lib/src/widgets/tiles/category_tile.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:widgetbook/src/models/organizers/organizers.dart'; -import 'package:widgetbook/src/navigation/organizer_provider.dart'; -import 'package:widgetbook/src/widgets/tiles/spaced_tile.dart'; -import 'package:widgetbook/src/widgets/tiles/tile_helper_methods.dart'; - -class CategoryTile extends StatelessWidget { - const CategoryTile({Key? key, required this.category}) : super(key: key); - - final WidgetbookCategory category; - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SpacedTile( - organizer: category, - level: 0, - iconData: Icons.category, - iconColor: Colors.red, - onClicked: () { - context.read().toggleExpander(category); - }, - ), - if (category.isExpanded) ...[ - ...buildFolders( - folders: category.folders, - currentLevel: 1, - ), - ...buildWidgets( - widgets: category.widgets, - currentLevel: 1, - ), - ] - ], - ); - } -} diff --git a/packages/widgetbook/lib/src/widgets/tiles/folder_tile.dart b/packages/widgetbook/lib/src/widgets/tiles/folder_tile.dart deleted file mode 100644 index c6b5b6b97..000000000 --- a/packages/widgetbook/lib/src/widgets/tiles/folder_tile.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:widgetbook/src/models/organizers/organizers.dart'; -import 'package:widgetbook/src/navigation/organizer_provider.dart'; -import 'package:widgetbook/src/utils/utils.dart'; -import 'package:widgetbook/src/widgets/tiles/spaced_tile.dart'; -import 'package:widgetbook/src/widgets/tiles/tile_helper_methods.dart'; - -class FolderTile extends StatefulWidget { - const FolderTile({ - Key? key, - required this.folder, - required this.level, - }) : super(key: key); - - final WidgetbookFolder folder; - final int level; - - @override - _FolderTileState createState() => _FolderTileState(); -} - -class _FolderTileState extends State { - bool hover = false; - - @override - Widget build(BuildContext context) { - final folder = widget.folder; - final isExpanded = widget.folder.isExpanded; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SpacedTile( - level: widget.level, - organizer: widget.folder, - iconData: isExpanded ? Icons.folder_open : Icons.folder, - iconColor: context.colorScheme.primary, - onClicked: () { - context.read().toggleExpander(widget.folder); - }, - ), - if (isExpanded) - ...buildFolders( - folders: folder.folders, - currentLevel: widget.level + 1, - ), - if (isExpanded) - ...buildWidgets( - widgets: folder.widgets, - currentLevel: widget.level + 1, - ) - ], - ); - } -} diff --git a/packages/widgetbook/lib/src/widgets/tiles/spaced_tile.dart b/packages/widgetbook/lib/src/widgets/tiles/spaced_tile.dart deleted file mode 100644 index 657d0e4f6..000000000 --- a/packages/widgetbook/lib/src/widgets/tiles/spaced_tile.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:widgetbook/src/models/organizers/organizer.dart'; -import 'package:widgetbook/src/widgets/tiles/tile.dart'; -import 'package:widgetbook/src/widgets/tiles/tile_spacer.dart'; - -class SpacedTile extends StatelessWidget { - const SpacedTile({ - Key? key, - required this.organizer, - required this.level, - required this.iconData, - required this.iconColor, - this.onClicked, - }) : super(key: key); - - final int level; - final Organizer organizer; - final IconData iconData; - final Color iconColor; - final VoidCallback? onClicked; - - @override - Widget build(BuildContext context) { - return Row( - children: [ - TileSpacer(level: level), - Expanded( - child: Tile( - iconData: iconData, - iconColor: iconColor, - organizer: organizer, - onClicked: onClicked, - ), - ), - ], - ); - } -} diff --git a/packages/widgetbook/lib/src/widgets/tiles/story_tile.dart b/packages/widgetbook/lib/src/widgets/tiles/story_tile.dart deleted file mode 100644 index 72fdefe4b..000000000 --- a/packages/widgetbook/lib/src/widgets/tiles/story_tile.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:widgetbook/src/models/organizers/organizers.dart'; -import 'package:widgetbook/src/navigation/preview_provider.dart'; -import 'package:widgetbook/src/navigation/router.dart'; -import 'package:widgetbook/src/utils/utils.dart'; -import 'package:widgetbook/src/widgets/tiles/spaced_tile.dart'; - -class StoryTile extends StatefulWidget { - const StoryTile({ - Key? key, - required this.useCase, - required this.level, - }) : super(key: key); - - final WidgetbookUseCase useCase; - final int level; - - @override - _StoryTileState createState() => _StoryTileState(); -} - -class _StoryTileState extends State { - @override - Widget build(BuildContext context) { - return SpacedTile( - level: widget.level, - organizer: widget.useCase, - iconData: Icons.auto_stories, - iconColor: Styles.storyColor, - onClicked: () { - context.read().selectUseCase(widget.useCase); - navigate(context); - }, - ); - } -} diff --git a/packages/widgetbook/lib/src/widgets/tiles/tile.dart b/packages/widgetbook/lib/src/widgets/tiles/tile.dart deleted file mode 100644 index 78a084e47..000000000 --- a/packages/widgetbook/lib/src/widgets/tiles/tile.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:widgetbook/src/constants/radii.dart'; -import 'package:widgetbook/src/navigation/preview_provider.dart'; -import 'package:widgetbook/src/utils/utils.dart'; -import 'package:widgetbook/src/widgets/expanders/expander_row.dart'; -import 'package:widgetbook/widgetbook.dart'; - -class Tile extends StatefulWidget { - const Tile({ - Key? key, - required this.iconData, - required this.iconColor, - required this.organizer, - this.onClicked, - }) : super(key: key); - - final IconData iconData; - final Color iconColor; - final Organizer organizer; - final VoidCallback? onClicked; - - static const double spacing = 8; - - @override - _TileState createState() => _TileState(); -} - -class _TileState extends State { - bool hovered = false; - - Widget _buildTile(BuildContext context) { - return Tooltip( - waitDuration: const Duration(milliseconds: 700), - message: widget.organizer.name, - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 4, - horizontal: 8, - ), - child: Row( - children: [ - Icon( - widget.iconData, - color: hovered ? context.colorScheme.onPrimary : widget.iconColor, - size: 16, - ), - const SizedBox( - width: Tile.spacing, - ), - Expanded( - child: Text( - widget.organizer.name, - overflow: TextOverflow.ellipsis, - ), - ), - if (hovered && widget.organizer is ExpandableOrganizer) - Padding( - padding: const EdgeInsets.only(right: 4), - child: ExpanderRow.small( - organizers: [widget.organizer as ExpandableOrganizer], - ), - ) - ], - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - final state = context.watch().state; - final isSelected = state.selectedUseCase == widget.organizer; - - return GestureDetector( - onTap: () { - widget.onClicked?.call(); - }, - child: MouseRegion( - onEnter: (e) { - setState(() => hovered = true); - }, - onExit: (e) { - setState(() => hovered = false); - }, - cursor: SystemMouseCursors.click, - child: AnimatedContainer( - decoration: BoxDecoration( - color: hovered || isSelected - ? Styles.getHighlightColor(context) - : null, - borderRadius: Radii.defaultRadius, - ), - duration: const Duration(milliseconds: 100), - child: _buildTile(context), - ), - ), - ); - } -} diff --git a/packages/widgetbook/lib/src/widgets/tiles/tile_helper_methods.dart b/packages/widgetbook/lib/src/widgets/tiles/tile_helper_methods.dart deleted file mode 100644 index 050e1e96e..000000000 --- a/packages/widgetbook/lib/src/widgets/tiles/tile_helper_methods.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:widgetbook/src/models/organizers/organizers.dart'; -import 'package:widgetbook/src/widgets/tiles/folder_tile.dart'; -import 'package:widgetbook/src/widgets/tiles/widget_tile.dart'; - -List buildFolders({ - required List folders, - required int currentLevel, -}) { - return folders - .map( - (WidgetbookFolder folder) => FolderTile( - folder: folder, - level: currentLevel, - ), - ) - .toList(); -} - -List buildWidgets({ - required List widgets, - required int currentLevel, -}) { - return widgets - .map( - (WidgetbookComponent widgetElement) => WidgetTile( - widgetElement: widgetElement, - level: currentLevel, - ), - ) - .toList(); -} diff --git a/packages/widgetbook/lib/src/widgets/tiles/tile_spacer.dart b/packages/widgetbook/lib/src/widgets/tiles/tile_spacer.dart deleted file mode 100644 index 8d3063568..000000000 --- a/packages/widgetbook/lib/src/widgets/tiles/tile_spacer.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter/material.dart'; - -class TileSpacer extends StatelessWidget { - const TileSpacer({ - Key? key, - required this.level, - }) : super(key: key); - - final int level; - - @override - Widget build(BuildContext context) { - return SizedBox( - width: level * 24, - ); - } -} diff --git a/packages/widgetbook/lib/src/widgets/tiles/widget_tile.dart b/packages/widgetbook/lib/src/widgets/tiles/widget_tile.dart deleted file mode 100644 index 064464548..000000000 --- a/packages/widgetbook/lib/src/widgets/tiles/widget_tile.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:widgetbook/src/models/organizers/organizers.dart'; -import 'package:widgetbook/src/navigation/organizer_provider.dart'; -import 'package:widgetbook/src/utils/utils.dart'; -import 'package:widgetbook/src/widgets/tiles/spaced_tile.dart'; -import 'package:widgetbook/src/widgets/tiles/story_tile.dart'; - -class WidgetTile extends StatefulWidget { - const WidgetTile({ - Key? key, - required this.widgetElement, - required this.level, - }) : super(key: key); - - final WidgetbookComponent widgetElement; - final int level; - - @override - _WidgetTileState createState() => _WidgetTileState(); -} - -class _WidgetTileState extends State { - bool expanded = false; - bool hover = false; - - List _buildStories(int level) { - final stories = widget.widgetElement.useCases; - - return stories - .map( - (WidgetbookUseCase story) => StoryTile( - useCase: story, - level: level, - ), - ) - .toList(); - } - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SpacedTile( - level: widget.level, - organizer: widget.widgetElement, - iconData: Icons.style, - iconColor: context.colorScheme.secondary, - onClicked: () { - context - .read() - .toggleExpander(widget.widgetElement); - }, - ), - if (widget.widgetElement.isExpanded) ..._buildStories(widget.level + 1), - ], - ); - } -} diff --git a/packages/widgetbook/lib/src/workbench/workbench.dart b/packages/widgetbook/lib/src/workbench/workbench.dart index 1c7d03020..d60e339c2 100644 --- a/packages/widgetbook/lib/src/workbench/workbench.dart +++ b/packages/widgetbook/lib/src/workbench/workbench.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:widgetbook/src/builder/provider/builder_provider.dart'; import 'package:widgetbook/src/constants/radii.dart'; -import 'package:widgetbook/src/navigation/preview_provider.dart'; +import 'package:widgetbook/src/navigation/providers/preview_provider.dart'; import 'package:widgetbook/src/utils/utils.dart'; import 'package:widgetbook/src/workbench/preview.dart'; import 'package:widgetbook/src/workbench/workbench_controls.dart'; diff --git a/packages/widgetbook/lib/widgetbook.dart b/packages/widgetbook/lib/widgetbook.dart index fbc70941a..53fe4dc22 100644 --- a/packages/widgetbook/lib/widgetbook.dart +++ b/packages/widgetbook/lib/widgetbook.dart @@ -1,8 +1,9 @@ export 'package:device_frame/device_frame.dart' hide DeviceType; export 'package:widgetbook/src/addons/addons.dart'; +export 'package:widgetbook/src/app_info/app_info.dart'; export 'package:widgetbook/src/devices/widgetbook_device_frame.dart'; export 'package:widgetbook/src/knobs/knobs.dart'; -export 'package:widgetbook/src/models/models.dart'; +export 'package:widgetbook/src/navigation/navigation.dart'; export 'package:widgetbook/src/theming/widgetbook_theme.dart'; export 'package:widgetbook/src/widgetbook.dart'; export 'package:widgetbook_models/widgetbook_models.dart'; diff --git a/packages/widgetbook/pubspec.lock b/packages/widgetbook/pubspec.lock index 3f65472cd..c18e6ff54 100644 --- a/packages/widgetbook/pubspec.lock +++ b/packages/widgetbook/pubspec.lock @@ -162,6 +162,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + equatable: + dependency: transitive + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -216,7 +223,7 @@ packages: name: freezed_annotation url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.2.0" frontend_server_client: dependency: transitive description: @@ -286,7 +293,7 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "4.5.0" + version: "4.7.0" logging: dependency: transitive description: @@ -558,12 +565,19 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" - widgetbook_models: + widgetbook_core: dependency: "direct main" description: - path: "../widgetbook_models" + path: "../widgetbook_core" relative: true source: path + version: "0.0.1" + widgetbook_models: + dependency: "direct main" + description: + name: widgetbook_models + url: "https://pub.dartlang.org" + source: hosted version: "3.0.0-beta.1" yaml: dependency: transitive @@ -573,5 +587,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.17.0 <3.0.0" + dart: ">=2.18.5 <3.0.0" flutter: ">=3.3.0" diff --git a/packages/widgetbook/pubspec.yaml b/packages/widgetbook/pubspec.yaml index ce0d7e953..f1c133a44 100644 --- a/packages/widgetbook/pubspec.yaml +++ b/packages/widgetbook/pubspec.yaml @@ -20,6 +20,8 @@ dependencies: meta: ^1.8.0 nested: ^1.0.0 provider: ^6.0.2 + widgetbook_core: + path: ../../packages/widgetbook_core widgetbook_models: ^3.0.0-beta.1 dev_dependencies: diff --git a/packages/widgetbook/test/src/models/app_info_test.dart b/packages/widgetbook/test/src/models/app_info_test.dart index c085a00d4..b6576f4cf 100644 --- a/packages/widgetbook/test/src/models/app_info_test.dart +++ b/packages/widgetbook/test/src/models/app_info_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:widgetbook/src/app_info/app_info.dart'; +import 'package:widgetbook/src/app_info/models/app_info.dart'; void main() { group( diff --git a/packages/widgetbook/test/src/navigation/helpers/widgetbook_use_case_helper_test.dart b/packages/widgetbook/test/src/navigation/helpers/widgetbook_use_case_helper_test.dart new file mode 100644 index 000000000..301f5b9bd --- /dev/null +++ b/packages/widgetbook/test/src/navigation/helpers/widgetbook_use_case_helper_test.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:widgetbook/src/navigation/helpers/widgetbook_use_case_helper.dart'; +import 'package:widgetbook/widgetbook.dart'; + +void main() { + final nodes = [ + WidgetbookPackage( + name: 'Package', + children: [ + WidgetbookCategory( + name: 'Category', + children: [ + WidgetbookComponent( + name: 'Component', + useCases: [ + WidgetbookUseCase( + name: 'UseCase 1', + builder: (context) => Container(), + ), + ], + ), + ], + ), + WidgetbookComponent( + name: 'Component', + useCases: [ + WidgetbookUseCase( + name: 'UseCase 2', + builder: (context) => Container(), + ), + ], + ) + ], + ), + WidgetbookComponent( + name: 'Component', + useCases: [ + WidgetbookUseCase( + name: 'UseCase 3', + builder: (context) => Container(), + ), + ], + ), + ]; + + group( + '$WidgetbookUseCaseHelper', + () { + test( + 'Can get all $WidgetbookUseCase items from navigation tree', + () { + final expectedUseCases = [ + WidgetbookUseCase( + name: 'UseCase 1', + builder: (context) => Container(), + ), + WidgetbookUseCase( + name: 'UseCase 2', + builder: (context) => Container(), + ), + WidgetbookUseCase( + name: 'UseCase 3', + builder: (context) => Container(), + ), + ]; + + expect( + WidgetbookUseCaseHelper.fromNodes(nodes), + equals(expectedUseCases), + ); + }, + ); + }, + // Todo: make this test work + skip: true, + ); +} diff --git a/packages/widgetbook/test/src/navigation/models/widgetbook_component_test.dart b/packages/widgetbook/test/src/navigation/models/widgetbook_component_test.dart new file mode 100644 index 000000000..63b691bb8 --- /dev/null +++ b/packages/widgetbook/test/src/navigation/models/widgetbook_component_test.dart @@ -0,0 +1,18 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:widgetbook/src/navigation/models/models.dart'; + +void main() { + group('$WidgetbookComponent', () { + test('Supports value equality', () { + final component1 = WidgetbookComponent( + name: 'Component', + useCases: [], + ); + final component2 = WidgetbookComponent( + name: 'Component', + useCases: [], + ); + expect(component1, equals(component2)); + }); + }); +} diff --git a/packages/widgetbook/test/src/navigation/models/widgetbook_use_case_test.dart b/packages/widgetbook/test/src/navigation/models/widgetbook_use_case_test.dart new file mode 100644 index 000000000..dd95c6692 --- /dev/null +++ b/packages/widgetbook/test/src/navigation/models/widgetbook_use_case_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:widgetbook/src/navigation/models/models.dart'; + +void main() { + group( + '$WidgetbookUseCase', + () { + test('Supports value equality', () { + final useCase1 = WidgetbookUseCase( + name: 'Use case', + builder: (_) => const Center(), + ); + final useCase2 = WidgetbookUseCase( + name: 'Use case', + builder: (_) => const Center(), + ); + + expect(useCase1, equals(useCase2)); + }); + }, + // Todo(Roaa94): make this test work + // Currently it's not working because the + // builder param equality doesn't work + skip: true, + ); +} diff --git a/packages/widgetbook/test/src/navigation/organizer_provider_test.dart b/packages/widgetbook/test/src/navigation/organizer_provider_test.dart deleted file mode 100644 index f9c13f960..000000000 --- a/packages/widgetbook/test/src/navigation/organizer_provider_test.dart +++ /dev/null @@ -1,531 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:widgetbook/src/models/models.dart'; -import 'package:widgetbook/src/navigation/organizer_provider.dart'; -import 'package:widgetbook/src/navigation/organizer_state.dart'; -import 'package:widgetbook/src/repositories/story_repository.dart'; -import 'package:widgetbook/src/services/filter_service.dart'; - -import '../../mocks/filter_service_mock.dart'; - -void main() { - late StoryRepository storyRepository; - - final story1 = WidgetbookUseCase( - name: 'Story 1', - builder: (context) => Container(), - ); - - final story2 = WidgetbookUseCase( - name: 'Story 2', - builder: (context) => Container(), - ); - - setUp( - () { - storyRepository = StoryRepository( - initialConfiguration: { - story1.name: story1, - story2.name: story2, - }, - ); - }, - ); - - group( - '$OrganizerProvider', - () { - test( - 'togglesExpander of $WidgetbookComponent', - () { - final widgetElement = WidgetbookComponent( - name: 'Widget 1', - useCases: [], - ); - - final provider = OrganizerProvider( - state: OrganizerState.unfiltered( - categories: [ - WidgetbookCategory( - name: 'Category 1', - widgets: [widgetElement], - ), - ], - ), - storyRepository: storyRepository, - )..toggleExpander(widgetElement); - - expect( - provider.state, - equals( - OrganizerState.unfiltered( - categories: [ - WidgetbookCategory( - name: 'Category 1', - widgets: [ - WidgetbookComponent( - name: 'Widget 1', - isExpanded: true, - useCases: [], - ) - ], - ), - ], - ), - ), - ); - }, - ); - - test( - 'togglesExpander of $WidgetbookComponent', - () { - final widgetbookCategory = WidgetbookCategory( - name: 'Category 1', - widgets: [ - WidgetbookComponent( - name: 'Widget 1', - useCases: [], - ) - ], - ); - - final provider = OrganizerProvider( - state: OrganizerState.unfiltered( - categories: [ - widgetbookCategory, - ], - ), - storyRepository: storyRepository, - )..setExpandedRecursive( - [widgetbookCategory], - expanded: true, - ); - - expect( - provider.state, - equals( - OrganizerState.unfiltered( - categories: [ - WidgetbookCategory( - name: 'Category 1', - widgets: [ - WidgetbookComponent( - name: 'Widget 1', - isExpanded: true, - useCases: [], - ) - ], - ), - ], - ), - ), - ); - }, - ); - - test( - 'recursive expander of $WidgetbookComponent', - () { - final widgetCategory = WidgetbookCategory( - name: 'Category 1', - folders: [ - WidgetbookFolder( - name: 'Folder 1-1', - folders: [ - WidgetbookFolder( - name: 'Folder 1-2', - widgets: [ - WidgetbookComponent( - name: 'Widget 1-2', - useCases: [], - ), - ], - ) - ], - ), - WidgetbookFolder( - name: 'Folder 2-1', - folders: [ - WidgetbookFolder( - name: 'Folder 2-2', - widgets: [ - WidgetbookComponent( - name: 'Widget 2-2', - useCases: [], - ), - ], - ) - ], - ), - ], - ); - - final provider = OrganizerProvider( - state: OrganizerState.unfiltered( - categories: [widgetCategory], - ), - storyRepository: storyRepository, - )..setExpandedRecursive( - [widgetCategory], - expanded: true, - ); - - expect( - provider.state, - equals( - OrganizerState.unfiltered( - categories: [ - WidgetbookCategory( - name: 'Category 1', - folders: [ - WidgetbookFolder( - name: 'Folder 1-1', - isExpanded: true, - folders: [ - WidgetbookFolder( - isExpanded: true, - name: 'Folder 1-2', - widgets: [ - WidgetbookComponent( - name: 'Widget 1-2', - useCases: [], - isExpanded: true, - ), - ], - ) - ], - ), - WidgetbookFolder( - name: 'Folder 2-1', - isExpanded: true, - folders: [ - WidgetbookFolder( - isExpanded: true, - name: 'Folder 2-2', - widgets: [ - WidgetbookComponent( - name: 'Widget 2-2', - useCases: [], - isExpanded: true, - ), - ], - ) - ], - ), - ], - ), - ], - ), - ), - ); - }, - ); - - test( - 'recursive expander of $WidgetbookComponent to false', - () { - final widgetCategory = WidgetbookCategory( - name: 'Category 1', - folders: [ - WidgetbookFolder( - name: 'Folder 1-1', - isExpanded: true, - folders: [ - WidgetbookFolder( - name: 'Folder 1-2', - widgets: [ - WidgetbookComponent( - name: 'Widget 1-2', - useCases: [], - ), - ], - ) - ], - ), - WidgetbookFolder( - name: 'Folder 2-1', - isExpanded: true, - folders: [ - WidgetbookFolder( - name: 'Folder 2-2', - widgets: [ - WidgetbookComponent( - name: 'Widget 2-2', - useCases: [], - ), - ], - ) - ], - ), - ], - ); - - final provider = OrganizerProvider( - state: OrganizerState.unfiltered( - categories: [widgetCategory], - ), - storyRepository: storyRepository, - )..setExpandedRecursive( - [widgetCategory], - expanded: false, - ); - - expect( - provider.state, - equals( - OrganizerState.unfiltered( - categories: [ - WidgetbookCategory( - name: 'Category 1', - isExpanded: false, - folders: [ - WidgetbookFolder( - name: 'Folder 1-1', - folders: [ - WidgetbookFolder( - name: 'Folder 1-2', - widgets: [ - WidgetbookComponent( - name: 'Widget 1-2', - useCases: [], - ), - ], - ) - ], - ), - WidgetbookFolder( - name: 'Folder 2-1', - folders: [ - WidgetbookFolder( - name: 'Folder 2-2', - widgets: [ - WidgetbookComponent( - name: 'Widget 2-2', - useCases: [], - ), - ], - ) - ], - ), - ], - ), - ], - ), - ), - ); - }, - ); - - test( - 'expands $WidgetbookComponent when the selected story changes', - () { - final provider = OrganizerProvider( - state: OrganizerState.unfiltered( - categories: [ - WidgetbookCategory( - name: 'Category 1', - folders: [ - WidgetbookFolder( - name: 'Folder 1', - ), - ], - widgets: [ - WidgetbookComponent( - name: 'Widget 1', - isExpanded: true, - useCases: [story1, story2], - ), - ], - ), - ], - ), - storyRepository: storyRepository, - )..hotReload( - [ - WidgetbookCategory( - name: 'Category 1', - folders: [ - WidgetbookFolder( - name: 'Folder 1', - ), - ], - widgets: [ - // Note that this WidgetElement does not have the isExpanded - // property set to true - WidgetbookComponent( - name: 'Widget 1', - useCases: [ - story1, - ], - ), - ], - ), - ], - ); - - // Its still expected that the new structures has the isExpanded - // property set to true - expect( - provider.state, - equals( - OrganizerState.unfiltered( - categories: [ - WidgetbookCategory( - name: 'Category 1', - folders: [ - WidgetbookFolder( - name: 'Folder 1', - ), - ], - widgets: [ - // Note that this WidgetElement does have the isExpanded - // property set to true - WidgetbookComponent( - name: 'Widget 1', - isExpanded: true, - useCases: [ - story1, - ], - ), - ], - ), - ], - ), - ), - ); - }, - ); - - test( - 'resets filter when resetFilter is called', - () { - final folder = WidgetbookFolder( - name: 'Folder 1', - ); - final category = WidgetbookCategory( - name: 'Category 1', - folders: [ - folder, - ], - widgets: [ - WidgetbookComponent( - name: 'Widget 1', - isExpanded: true, - useCases: [ - story1, - story2, - ], - ), - ], - ); - - final filteredCategory = WidgetbookCategory( - name: 'Category 1', - folders: [ - folder, - ], - ); - - final provider = OrganizerProvider( - state: OrganizerState( - allCategories: [ - category, - ], - filteredCategories: [ - filteredCategory, - ], - searchTerm: 'does not really matter', - ), - storyRepository: storyRepository, - )..resetFilter(); - - expect( - provider.state, - equals( - OrganizerState.unfiltered( - categories: [ - category, - ], - ), - ), - ); - }, - ); - - test( - 'invokes $FilterService when filter is called', - () { - final folder = WidgetbookFolder( - name: 'Folder 1', - ); - final category = WidgetbookCategory( - name: 'Category 1', - folders: [ - folder, - ], - widgets: [ - WidgetbookComponent( - name: 'Widget 1', - isExpanded: true, - useCases: [ - story1, - story2, - ], - ), - ], - ); - - const searchTerm = 'does not really matter'; - - final filterService = FilterServiceMock(); - when( - () => filterService.filter( - searchTerm, - [ - category, - ], - ), - ).thenReturn( - [ - category, - ], - ); - - final provider = OrganizerProvider( - state: OrganizerState.unfiltered( - categories: [ - category, - ], - ), - storyRepository: storyRepository, - filterService: filterService, - )..filter(searchTerm); - - verify( - () => filterService.filter( - searchTerm, - [ - category, - ], - ), - ).called(1); - - expect( - provider.state, - equals( - OrganizerState( - allCategories: [ - category, - ], - filteredCategories: [category], - searchTerm: searchTerm, - ), - ), - ); - }, - ); - }, - ); -} diff --git a/packages/widgetbook/test/src/navigation/providers/navigation_tree_provider_test.dart b/packages/widgetbook/test/src/navigation/providers/navigation_tree_provider_test.dart new file mode 100644 index 000000000..e9d13368e --- /dev/null +++ b/packages/widgetbook/test/src/navigation/providers/navigation_tree_provider_test.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:widgetbook/src/navigation/navigation.dart'; +import 'package:widgetbook/src/repositories/story_repository.dart'; +import 'package:widgetbook_core/widgetbook_core.dart'; + +void main() { + late NavigationTreeProvider navigationTreeProvider; + + late StoryRepository storyRepository; + + final story1 = WidgetbookUseCase( + name: 'Story 1', + builder: (context) => Container(), + ); + + final story2 = WidgetbookUseCase( + name: 'Story 2', + builder: (context) => Container(), + ); + + final nodes = [ + WidgetbookPackage( + name: 'Package', + children: [ + WidgetbookCategory(name: 'Category', children: const []), + WidgetbookComponent( + name: 'Component', + useCases: [ + WidgetbookUseCase( + name: 'UseCase', + builder: (context) => Container(), + ), + ], + ) + ], + ), + WidgetbookComponent( + name: 'Component', + useCases: [ + WidgetbookUseCase( + name: 'UseCase', + builder: (context) => Container(), + ), + ], + ), + ]; + + setUp(() { + storyRepository = StoryRepository( + initialConfiguration: { + story1.name: story1, + story2.name: story2, + }, + ); + + navigationTreeProvider = NavigationTreeProvider( + state: NavigationTreeState( + nodes: nodes, + ), + storyRepository: storyRepository, + ); + }); + + group('$NavigationTreeProvider', () { + test( + 'Can filter navigation nodes', + () { + final testNodes = [ + WidgetbookPackage(name: 'Package', children: const []), + WidgetbookComponent( + name: 'Component', + useCases: [ + WidgetbookUseCase( + name: 'Use case', + builder: (_) => Container(), + ), + ], + ), + ]; + navigationTreeProvider.state = navigationTreeProvider.state.copyWith( + nodes: testNodes, + filteredNodes: testNodes, + ); + + final results = [ + MultiChildNavigationNodeData( + name: 'Component', + type: NavigationNodeType.component, + children: [ + WidgetbookUseCase( + name: 'Use case', + builder: (_) => Container(), + ), + ], + ), + ]; + + navigationTreeProvider.filter('use case'); + expect( + navigationTreeProvider.state.filteredNodes, + equals(results), + ); + }, + //Todo(Roaa94): make this test work + // Currently it's not working because the `WidgetbookUseCase` builder + // param equality doesn't work + skip: true, + ); + }); +} diff --git a/packages/widgetbook/test/src/navigation/preview_provider_test.dart b/packages/widgetbook/test/src/navigation/providers/preview_provider_test.dart similarity index 95% rename from packages/widgetbook/test/src/navigation/preview_provider_test.dart rename to packages/widgetbook/test/src/navigation/providers/preview_provider_test.dart index c7d1da22a..749bd23ff 100644 --- a/packages/widgetbook/test/src/navigation/preview_provider_test.dart +++ b/packages/widgetbook/test/src/navigation/providers/preview_provider_test.dart @@ -1,10 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:widgetbook/src/models/models.dart'; -import 'package:widgetbook/src/navigation/preview_provider.dart'; -import 'package:widgetbook/src/navigation/preview_state.dart'; import 'package:widgetbook/src/repositories/selected_story_repository.dart'; import 'package:widgetbook/src/repositories/story_repository.dart'; +import 'package:widgetbook/widgetbook.dart'; void main() { late StoryRepository storyRepository; diff --git a/packages/widgetbook/test/src/repositories/memory_repository_test.dart b/packages/widgetbook/test/src/repositories/memory_repository_test.dart index 15023188c..87d390295 100644 --- a/packages/widgetbook/test/src/repositories/memory_repository_test.dart +++ b/packages/widgetbook/test/src/repositories/memory_repository_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:widgetbook/src/models/model.dart'; import 'package:widgetbook/src/repositories/memory_repository.dart'; +import 'package:widgetbook_core/widgetbook_core.dart'; class _Item extends Model { _Item({ diff --git a/packages/widgetbook/test/src/services/filter_service_test.dart b/packages/widgetbook/test/src/services/filter_service_test.dart deleted file mode 100644 index f7a3004b0..000000000 --- a/packages/widgetbook/test/src/services/filter_service_test.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:widgetbook/src/models/models.dart'; -import 'package:widgetbook/src/services/filter_service.dart'; - -void main() { - final story1 = WidgetbookUseCase( - name: 'Story 1', - builder: (context) => Container(), - ); - - final story2 = WidgetbookUseCase( - name: 'Story 2', - builder: (context) => Container(), - ); - - group( - '$FilterService', - () { - test( - 'filters $WidgetbookComponent', - () async { - final widget1 = WidgetbookComponent( - name: 'Widget 1', - isExpanded: true, - useCases: [ - story1, - story2, - ], - ); - - final widget2 = WidgetbookComponent( - name: 'Widget 2', - isExpanded: true, - useCases: [ - story1, - story2, - ], - ); - - final category = WidgetbookCategory( - name: 'Category 1', - folders: [ - WidgetbookFolder( - name: 'Folder 1', - ), - ], - widgets: [ - widget1, - widget2, - ], - ); - - final searchTerm = widget1.name; - - const service = FilterService(); - final result = service.filter( - searchTerm, - [ - category, - ], - ); - - expect( - result, - equals( - [ - WidgetbookCategory( - name: 'Category 1', - folders: [], - widgets: [ - widget1, - ], - ), - ], - ), - ); - }, - ); - - test( - 'filters $WidgetbookFolder', - () async { - final folder1_1 = WidgetbookFolder(name: 'Folder1.1'); - final folder1_2 = WidgetbookFolder(name: 'Folder1.2'); - final folder1 = WidgetbookFolder( - name: 'Folder 1', - folders: [ - folder1_1, - folder1_2, - ], - ); - final folder2 = WidgetbookFolder(name: 'Folder 2'); - - final widget1 = WidgetbookComponent( - name: 'Widget 1', - isExpanded: true, - useCases: [ - story1, - story2, - ], - ); - - final category = WidgetbookCategory( - name: 'Category 1', - folders: [ - folder1, - folder2, - ], - widgets: [ - widget1, - ], - ); - - final searchTerm = folder1_1.name; - - const service = FilterService(); - final result = service.filter( - searchTerm, - [ - category, - ], - ); - - expect( - result, - equals( - [ - WidgetbookCategory( - name: 'Category 1', - folders: [ - WidgetbookFolder( - name: 'Folder 1', - folders: [ - folder1_1, - ], - ), - ], - widgets: [], - ), - ], - ), - ); - }, - ); - }, - ); -} diff --git a/packages/widgetbook/test/src/widgetbook_test.dart b/packages/widgetbook/test/src/widgetbook_test.dart index ad83a2049..a2fb8f129 100644 --- a/packages/widgetbook/test/src/widgetbook_test.dart +++ b/packages/widgetbook/test/src/widgetbook_test.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_core/widgetbook_core.dart'; Matcher expectAssertionErrorWithMessage({ required String message, @@ -23,24 +24,23 @@ void main() { name: 'A', ); - final categories = [ - WidgetbookCategory(name: 'A'), + final children = [ + WidgetbookCategory(name: 'A', children: const []), ]; group( 'constructor throws $AssertionError when', () { test( - 'categories is empty', + 'Navigation tree children are empty', () { expect( () => Widgetbook( addons: const [], - categories: const [], appInfo: appInfo, ), expectAssertionErrorWithMessage( - message: 'Please specify at least one $WidgetbookCategory.', + message: 'Please specify at least one $MultiChildNavigationNodeData.', ), ); }, @@ -64,7 +64,6 @@ void main() { ), ) ], - categories: categories, appInfo: appInfo, ), expectAssertionErrorWithMessage( @@ -86,7 +85,6 @@ void main() { ), ) ], - categories: categories, appInfo: appInfo, ), expectAssertionErrorWithMessage( @@ -108,7 +106,6 @@ void main() { ), ), ], - categories: categories, appInfo: appInfo, ), expectAssertionErrorWithMessage( @@ -130,7 +127,6 @@ void main() { ), ), ], - categories: categories, appInfo: appInfo, ), expectAssertionErrorWithMessage( @@ -153,7 +149,6 @@ void main() { ), ) ], - categories: categories, appInfo: appInfo, ), expectAssertionErrorWithMessage( diff --git a/packages/widgetbook/test/src/widgets/search_bar_test.dart b/packages/widgetbook/test/src/widgets/search_bar_test.dart deleted file mode 100644 index 9253b426f..000000000 --- a/packages/widgetbook/test/src/widgets/search_bar_test.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:provider/provider.dart'; -import 'package:widgetbook/src/navigation/organizer_provider.dart'; -import 'package:widgetbook/src/navigation/organizer_state.dart'; -import 'package:widgetbook/src/repositories/story_repository.dart'; -import 'package:widgetbook/src/widgets/search_bar.dart'; - -import '../../helper/widget_test_helper.dart'; - -Finder _findTextField() { - final textFieldFinder = find.byKey( - Key( - '$SearchBar.$TextField', - ), - ); - expect(textFieldFinder, findsOneWidget); - return textFieldFinder; -} - -Finder _findCancelButton() { - final cancelSearchButton = find.byKey( - Key('$SearchBar.CancelSearchButton'), - ); - - expect(cancelSearchButton, findsOneWidget); - return cancelSearchButton; -} - -void _expectNoCancelButton() { - final cancelSearchButton = find.byKey( - Key('$SearchBar.CancelSearchButton'), - ); - - expect(cancelSearchButton, findsNothing); -} - -void _expectEmptyTextField(String previousValue) { - final textFinder = find.text(previousValue); - expect(textFinder, findsNothing); -} - -void main() { - group( - '$SearchBar', - () { - testWidgets( - 'behaves correctly', - (WidgetTester tester) async { - final organizerProvider = OrganizerProvider( - state: OrganizerState.unfiltered( - categories: [], - ), - storyRepository: StoryRepository(), - ); - await tester.pumpWidgetWithMaterialApp( - ChangeNotifierProvider.value( - value: organizerProvider, - child: const SearchBar(), - ), - ); - - _expectNoCancelButton(); - - const searchTerm = 'Test'; - final textFieldFinder = _findTextField(); - - await tester.enterText( - textFieldFinder, - searchTerm, - ); - await tester.pump(); - - final cancelSearchButton = _findCancelButton(); - - await tester.tap(cancelSearchButton); - await tester.pump(); - - _expectNoCancelButton(); - _expectEmptyTextField(searchTerm); - }, - ); - }, - ); -} diff --git a/packages/widgetbook/test/src/widgets/tiles/tile_spacer_test.dart b/packages/widgetbook/test/src/widgets/tiles/tile_spacer_test.dart deleted file mode 100644 index 8a588156a..000000000 --- a/packages/widgetbook/test/src/widgets/tiles/tile_spacer_test.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:widgetbook/src/widgets/tiles/tile_spacer.dart'; - -import '../../../helper/widget_test_helper.dart'; - -void _testLevel(int level, double expectedWidth) { - testWidgets( - 'has width of $expectedWidth for level $level', - (WidgetTester tester) async { - await tester.pumpWidgetWithMaterialApp( - TileSpacer( - level: level, - ), - ); - - final tileSpacerFinder = find.byType(TileSpacer); - final size = tester.getSize(tileSpacerFinder); - - expect( - size, - equals( - Size(expectedWidth, 0), - ), - ); - }, - ); -} - -void main() { - group( - '$TileSpacer', - () { - _testLevel(0, 0); - _testLevel(1, 24); - _testLevel(2, 48); - }, - ); -} diff --git a/packages/widgetbook_core/lib/src/core/core.dart b/packages/widgetbook_core/lib/src/core/core.dart new file mode 100644 index 000000000..7e2ae2956 --- /dev/null +++ b/packages/widgetbook_core/lib/src/core/core.dart @@ -0,0 +1 @@ +export './models/models.dart'; diff --git a/packages/widgetbook/lib/src/models/model.dart b/packages/widgetbook_core/lib/src/core/models/model.dart similarity index 100% rename from packages/widgetbook/lib/src/models/model.dart rename to packages/widgetbook_core/lib/src/core/models/model.dart diff --git a/packages/widgetbook_core/lib/src/core/models/models.dart b/packages/widgetbook_core/lib/src/core/models/models.dart new file mode 100644 index 000000000..17865634a --- /dev/null +++ b/packages/widgetbook_core/lib/src/core/models/models.dart @@ -0,0 +1 @@ +export './model.dart'; diff --git a/packages/widgetbook_core/lib/src/navigation_tree/models/leaf_navigation_node_data.dart b/packages/widgetbook_core/lib/src/navigation_tree/models/leaf_navigation_node_data.dart new file mode 100644 index 000000000..8572cb5be --- /dev/null +++ b/packages/widgetbook_core/lib/src/navigation_tree/models/leaf_navigation_node_data.dart @@ -0,0 +1,9 @@ +import 'package:widgetbook_core/widgetbook_core.dart'; + +class LeafNavigationNodeData extends NavigationTreeNodeData { + LeafNavigationNodeData({ + required super.name, + required super.type, + super.isInitiallyExpanded, + }) : super(children: []); +} diff --git a/packages/widgetbook_core/lib/src/navigation_tree/models/models.dart b/packages/widgetbook_core/lib/src/navigation_tree/models/models.dart index e37cd9556..82d96432c 100644 --- a/packages/widgetbook_core/lib/src/navigation_tree/models/models.dart +++ b/packages/widgetbook_core/lib/src/navigation_tree/models/models.dart @@ -1 +1,3 @@ +export './leaf_navigation_node_data.dart'; +export './multi_child_navigation_node_data.dart'; export './navigation_tree_node_data.dart'; diff --git a/packages/widgetbook_core/lib/src/navigation_tree/models/multi_child_navigation_node_data.dart b/packages/widgetbook_core/lib/src/navigation_tree/models/multi_child_navigation_node_data.dart new file mode 100644 index 000000000..ff2014fdd --- /dev/null +++ b/packages/widgetbook_core/lib/src/navigation_tree/models/multi_child_navigation_node_data.dart @@ -0,0 +1,47 @@ +import 'package:widgetbook_core/widgetbook_core.dart'; + +class MultiChildNavigationNodeData extends NavigationTreeNodeData { + MultiChildNavigationNodeData({ + required super.name, + required super.children, + required super.type, + super.isInitiallyExpanded, + }); + + MultiChildNavigationNodeData updateChildren({ + required List children, + }) { + return MultiChildNavigationNodeData( + name: name, + type: type, + isInitiallyExpanded: isInitiallyExpanded, + children: children, + ); + } + + MultiChildNavigationNodeData? filterNode(String searchQuery) { + final regex = RegExp(searchQuery, caseSensitive: false); + if (name.contains(regex) && children.isNotEmpty) { + return this; + } + final matchingChildren = []; + for (final child in children) { + if (child is MultiChildNavigationNodeData) { + final matchingChildNode = child.filterNode(searchQuery); + if (matchingChildNode != null) { + matchingChildren.add(matchingChildNode); + } + } else { + if (child.name.contains(regex)) { + matchingChildren.add(child); + } + } + } + if (matchingChildren.isNotEmpty) { + return updateChildren( + children: matchingChildren, + ); + } + return null; + } +} diff --git a/packages/widgetbook_core/lib/src/navigation_tree/models/navigation_tree_node_data.dart b/packages/widgetbook_core/lib/src/navigation_tree/models/navigation_tree_node_data.dart index 2e3c7b1e1..ac0428d9a 100644 --- a/packages/widgetbook_core/lib/src/navigation_tree/models/navigation_tree_node_data.dart +++ b/packages/widgetbook_core/lib/src/navigation_tree/models/navigation_tree_node_data.dart @@ -1,21 +1,70 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:widgetbook_core/src/navigation_tree/enums/navigation_node_type.dart'; -part 'navigation_tree_node_data.freezed.dart'; +class NavigationTreeNodeData { + NavigationTreeNodeData({ + this.id, + required this.name, + required this.type, + this.children = const [], + this.isInitiallyExpanded = true, + }) { + for (final child in children) { + child.parent = this; + } + } -@freezed -class NavigationTreeNodeData with _$NavigationTreeNodeData { - const factory NavigationTreeNodeData({ - String? id, - required String name, - required NavigationNodeType type, - @Default([]) List children, - }) = _NavigationTreeNodeData; - - const NavigationTreeNodeData._(); + final String? id; + final String name; + final NavigationNodeType type; + final List children; + final bool isInitiallyExpanded; Widget get icon => type.icon; + bool get isExpandable => type.isExpandable; + bool get isSelectable => type.isSelectable; + + /// Used for navigation and matching hot reloaded elements with existing + String get path { + var path = name.replaceAll(' ', '-').toLowerCase(); + var current = parent; + while (current?.parent != null) { + path = '${current!.name.replaceAll(' ', '-').toLowerCase()}${'/$path'}'; + current = current.parent; + } + return path; + } + + /// The Node hosting this element. + NavigationTreeNodeData? parent; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return other is NavigationTreeNodeData && + other.id == id && + other.type == type && + other.name == name && + listEquals(other.children, children) && + other.isInitiallyExpanded == other.isInitiallyExpanded; + } + + @override + int get hashCode => name.hashCode; + + @override + String toString() { + return 'NavigationTreeNodeData(' + 'id: $id, ' + 'name: $name, ' + 'type: $type, ' + 'children: $children, ' + 'isInitiallyExpanded: $isInitiallyExpanded ' + ')'; + } } diff --git a/packages/widgetbook_core/lib/src/navigation_tree/models/navigation_tree_node_data.freezed.dart b/packages/widgetbook_core/lib/src/navigation_tree/models/navigation_tree_node_data.freezed.dart deleted file mode 100644 index cc868eb66..000000000 --- a/packages/widgetbook_core/lib/src/navigation_tree/models/navigation_tree_node_data.freezed.dart +++ /dev/null @@ -1,209 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target - -part of 'navigation_tree_node_data.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -/// @nodoc -mixin _$NavigationTreeNodeData { - String? get id => throw _privateConstructorUsedError; - String get name => throw _privateConstructorUsedError; - NavigationNodeType get type => throw _privateConstructorUsedError; - List get children => - throw _privateConstructorUsedError; - - @JsonKey(ignore: true) - $NavigationTreeNodeDataCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $NavigationTreeNodeDataCopyWith<$Res> { - factory $NavigationTreeNodeDataCopyWith(NavigationTreeNodeData value, - $Res Function(NavigationTreeNodeData) then) = - _$NavigationTreeNodeDataCopyWithImpl<$Res, NavigationTreeNodeData>; - @useResult - $Res call( - {String? id, - String name, - NavigationNodeType type, - List children}); -} - -/// @nodoc -class _$NavigationTreeNodeDataCopyWithImpl<$Res, - $Val extends NavigationTreeNodeData> - implements $NavigationTreeNodeDataCopyWith<$Res> { - _$NavigationTreeNodeDataCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = freezed, - Object? name = null, - Object? type = null, - Object? children = null, - }) { - return _then(_value.copyWith( - id: freezed == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String?, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - type: null == type - ? _value.type - : type // ignore: cast_nullable_to_non_nullable - as NavigationNodeType, - children: null == children - ? _value.children - : children // ignore: cast_nullable_to_non_nullable - as List, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$_NavigationTreeNodeDataCopyWith<$Res> - implements $NavigationTreeNodeDataCopyWith<$Res> { - factory _$$_NavigationTreeNodeDataCopyWith(_$_NavigationTreeNodeData value, - $Res Function(_$_NavigationTreeNodeData) then) = - __$$_NavigationTreeNodeDataCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String? id, - String name, - NavigationNodeType type, - List children}); -} - -/// @nodoc -class __$$_NavigationTreeNodeDataCopyWithImpl<$Res> - extends _$NavigationTreeNodeDataCopyWithImpl<$Res, - _$_NavigationTreeNodeData> - implements _$$_NavigationTreeNodeDataCopyWith<$Res> { - __$$_NavigationTreeNodeDataCopyWithImpl(_$_NavigationTreeNodeData _value, - $Res Function(_$_NavigationTreeNodeData) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = freezed, - Object? name = null, - Object? type = null, - Object? children = null, - }) { - return _then(_$_NavigationTreeNodeData( - id: freezed == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String?, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - type: null == type - ? _value.type - : type // ignore: cast_nullable_to_non_nullable - as NavigationNodeType, - children: null == children - ? _value._children - : children // ignore: cast_nullable_to_non_nullable - as List, - )); - } -} - -/// @nodoc - -class _$_NavigationTreeNodeData extends _NavigationTreeNodeData { - const _$_NavigationTreeNodeData( - {this.id, - required this.name, - required this.type, - final List children = const []}) - : _children = children, - super._(); - - @override - final String? id; - @override - final String name; - @override - final NavigationNodeType type; - final List _children; - @override - @JsonKey() - List get children { - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_children); - } - - @override - String toString() { - return 'NavigationTreeNodeData(id: $id, name: $name, type: $type, children: $children)'; - } - - @override - bool operator ==(dynamic other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$_NavigationTreeNodeData && - (identical(other.id, id) || other.id == id) && - (identical(other.name, name) || other.name == name) && - (identical(other.type, type) || other.type == type) && - const DeepCollectionEquality().equals(other._children, _children)); - } - - @override - int get hashCode => Object.hash(runtimeType, id, name, type, - const DeepCollectionEquality().hash(_children)); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$_NavigationTreeNodeDataCopyWith<_$_NavigationTreeNodeData> get copyWith => - __$$_NavigationTreeNodeDataCopyWithImpl<_$_NavigationTreeNodeData>( - this, _$identity); -} - -abstract class _NavigationTreeNodeData extends NavigationTreeNodeData { - const factory _NavigationTreeNodeData( - {final String? id, - required final String name, - required final NavigationNodeType type, - final List children}) = _$_NavigationTreeNodeData; - const _NavigationTreeNodeData._() : super._(); - - @override - String? get id; - @override - String get name; - @override - NavigationNodeType get type; - @override - List get children; - @override - @JsonKey(ignore: true) - _$$_NavigationTreeNodeDataCopyWith<_$_NavigationTreeNodeData> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/packages/widgetbook_core/lib/src/navigation_tree/widgets/navigation_tree.dart b/packages/widgetbook_core/lib/src/navigation_tree/widgets/navigation_tree.dart index e14afc0ec..c1ac3cdfa 100644 --- a/packages/widgetbook_core/lib/src/navigation_tree/widgets/navigation_tree.dart +++ b/packages/widgetbook_core/lib/src/navigation_tree/widgets/navigation_tree.dart @@ -1,35 +1,28 @@ import 'package:flutter/material.dart'; import 'package:widgetbook_core/src/navigation_tree/navigation_tree.dart'; -class NavigationTree extends StatefulWidget { +class NavigationTree extends StatelessWidget { const NavigationTree({ super.key, required this.nodes, this.onNodeSelected, + this.selectedNode, }); final List nodes; + final NavigationTreeNodeData? selectedNode; final ValueChanged? onNodeSelected; - @override - State createState() => _NavigationTreeState(); -} - -class _NavigationTreeState extends State { - String? selectedNodeId; - @override Widget build(BuildContext context) { return ListView.builder( - itemCount: widget.nodes.length, + padding: const EdgeInsets.symmetric(horizontal: 12), + itemCount: nodes.length, itemBuilder: (context, index) => NavigationTreeNode( - data: widget.nodes[index], - selectedNodeId: selectedNodeId, + data: nodes[index], + selectedNode: selectedNode, onNodeSelected: (NavigationTreeNodeData node) { - setState(() { - selectedNodeId = node.id; - }); - widget.onNodeSelected?.call(node); + onNodeSelected?.call(node); }, ), ); diff --git a/packages/widgetbook_core/lib/src/navigation_tree/widgets/navigation_tree_item.dart b/packages/widgetbook_core/lib/src/navigation_tree/widgets/navigation_tree_item.dart index 2a2cc00f3..24fc84d8c 100644 --- a/packages/widgetbook_core/lib/src/navigation_tree/widgets/navigation_tree_item.dart +++ b/packages/widgetbook_core/lib/src/navigation_tree/widgets/navigation_tree_item.dart @@ -41,7 +41,8 @@ class _NavigationTreeItemState extends State { }, child: Material( elevation: isSelected ? 3 : 0, - shadowColor: Colors.black, + shadowColor: isSelected ? Colors.black : Colors.transparent, + color: Colors.transparent, borderRadius: BorderRadius.circular(NavigationTreeItem.iconSize), child: ClipRRect( borderRadius: BorderRadius.circular(NavigationTreeItem.iconSize), diff --git a/packages/widgetbook_core/lib/src/navigation_tree/widgets/navigation_tree_node.dart b/packages/widgetbook_core/lib/src/navigation_tree/widgets/navigation_tree_node.dart index 0ca050c6a..027180c71 100644 --- a/packages/widgetbook_core/lib/src/navigation_tree/widgets/navigation_tree_node.dart +++ b/packages/widgetbook_core/lib/src/navigation_tree/widgets/navigation_tree_node.dart @@ -4,17 +4,15 @@ import 'package:widgetbook_core/widgetbook_core.dart'; class NavigationTreeNode extends StatefulWidget { const NavigationTreeNode({ super.key, - required this.data, this.level = 0, - this.initiallyExpanded = true, - this.selectedNodeId, + required this.data, + this.selectedNode, this.onNodeSelected, }); - final NavigationTreeNodeData data; final int level; - final bool initiallyExpanded; - final String? selectedNodeId; + final NavigationTreeNodeData data; + final NavigationTreeNodeData? selectedNode; final ValueChanged? onNodeSelected; @override @@ -26,7 +24,7 @@ class _NavigationTreeNodeState extends State { @override void initState() { - isExpanded = widget.initiallyExpanded; + isExpanded = widget.data.isInitiallyExpanded; super.initState(); } @@ -38,7 +36,7 @@ class _NavigationTreeNodeState extends State { data: widget.data, isExpanded: isExpanded, level: widget.level, - isSelected: widget.data.id == widget.selectedNodeId, + isSelected: widget.data.id == widget.selectedNode?.id, onTap: () { setState(() { isExpanded = !isExpanded; @@ -65,7 +63,7 @@ class _NavigationTreeNodeState extends State { itemBuilder: (context, index) => NavigationTreeNode( data: widget.data.children[index], level: widget.level + 1, - selectedNodeId: widget.selectedNodeId, + selectedNode: widget.selectedNode, onNodeSelected: widget.onNodeSelected, ), ), diff --git a/packages/widgetbook_core/lib/src/search/widgets/search_field.dart b/packages/widgetbook_core/lib/src/search/widgets/search_field.dart index 963a4de1e..d42546d12 100644 --- a/packages/widgetbook_core/lib/src/search/widgets/search_field.dart +++ b/packages/widgetbook_core/lib/src/search/widgets/search_field.dart @@ -4,9 +4,13 @@ class SearchField extends StatefulWidget { const SearchField({ super.key, this.onSearchPressed, + this.onSearchChanged, + this.onSearchCancelled, }); final VoidCallback? onSearchPressed; + final ValueChanged? onSearchChanged; + final VoidCallback? onSearchCancelled; @override State createState() => _SearchFieldState(); @@ -26,6 +30,7 @@ class _SearchFieldState extends State { setState(() { _searchValue = value; }); + widget.onSearchChanged?.call(value); }, decoration: InputDecoration( hintText: 'Search', @@ -66,6 +71,7 @@ class _SearchFieldState extends State { }); textEditingController.clear(); focusNode.unfocus(); + widget.onSearchCancelled?.call(); }, hoverColor: Colors.white.withOpacity(0.2), icon: const Padding( diff --git a/packages/widgetbook_core/lib/widgetbook_core.dart b/packages/widgetbook_core/lib/widgetbook_core.dart index 282f5f1c6..9064b343e 100644 --- a/packages/widgetbook_core/lib/widgetbook_core.dart +++ b/packages/widgetbook_core/lib/widgetbook_core.dart @@ -1,5 +1,6 @@ library widgetbook_core; +export './src/core/core.dart'; export './src/icons/icons.dart'; export './src/navigation_tree/navigation_tree.dart'; export './src/search/search.dart'; diff --git a/packages/widgetbook_core/pubspec.yaml b/packages/widgetbook_core/pubspec.yaml index 3031def34..69492f76b 100644 --- a/packages/widgetbook_core/pubspec.yaml +++ b/packages/widgetbook_core/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: flutter: sdk: flutter freezed_annotation: ^2.2.0 + collection: ^1.15.0 dev_dependencies: build_runner: ^2.3.0 diff --git a/packages/widgetbook/test/src/models/model_test.dart b/packages/widgetbook_core/test/src/core/models/model_test.dart similarity index 83% rename from packages/widgetbook/test/src/models/model_test.dart rename to packages/widgetbook_core/test/src/core/models/model_test.dart index 6cecbd062..e4e1ac36c 100644 --- a/packages/widgetbook/test/src/models/model_test.dart +++ b/packages/widgetbook_core/test/src/core/models/model_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:widgetbook/src/models/model.dart'; +import 'package:widgetbook_core/widgetbook_core.dart'; void main() { group( diff --git a/packages/widgetbook_core/test/src/navigation_tree/models/multi_child_navigation_node_test.dart b/packages/widgetbook_core/test/src/navigation_tree/models/multi_child_navigation_node_test.dart new file mode 100644 index 000000000..715e37e77 --- /dev/null +++ b/packages/widgetbook_core/test/src/navigation_tree/models/multi_child_navigation_node_test.dart @@ -0,0 +1,193 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:widgetbook_core/widgetbook_core.dart'; + +void main() { + group('$MultiChildNavigationNodeData', () { + final data = MultiChildNavigationNodeData( + name: 'Parent', + type: NavigationNodeType.package, + children: [ + NavigationTreeNodeData( + name: 'First child', + type: NavigationNodeType.package, + children: [ + NavigationTreeNodeData( + name: 'Second child', + type: NavigationNodeType.package, + children: [ + NavigationTreeNodeData( + name: 'Third child', + type: NavigationNodeType.package, + ), + ], + ), + ], + ), + ], + ); + + test('Can generate path', () { + expect( + data.children[0].children[0].children[0].path, + equals('first-child/second-child/third-child'), + ); + }); + + test( + 'Path is assigned to id', + () { + expect( + data.children[0].id, + equals(data.children[0].path), + ); + }, + // Todo: make this test work + skip: true, + ); + + test('Updates children', () { + final updatedData = data.updateChildren( + children: [], + ); + + expect(updatedData.children, isEmpty); + }); + + group('$MultiChildNavigationNodeData.filterNode', () { + test('Returns null when node has no matches', () { + final data = MultiChildNavigationNodeData( + name: 'Parent node', + type: NavigationNodeType.package, + children: [ + MultiChildNavigationNodeData( + name: 'First child node', + type: NavigationNodeType.package, + children: [ + LeafNavigationNodeData( + name: 'First leaf node', + type: NavigationNodeType.package, + ) + ], + ), + ], + ); + + expect(data.filterNode('foo'), isNull); + }); + + test('Can filter nodes on the first level', () { + final data = MultiChildNavigationNodeData( + name: 'Parent node', + type: NavigationNodeType.package, + children: [ + MultiChildNavigationNodeData( + name: 'First child node', + type: NavigationNodeType.package, + children: const [], + ), + MultiChildNavigationNodeData( + name: 'Second child node', + type: NavigationNodeType.package, + children: const [], + ), + ], + ); + + expect( + data.filterNode('parent'), + equals(data), + ); + }); + + test('Can filter nested nodes', () { + final data = MultiChildNavigationNodeData( + name: 'Parent node', + type: NavigationNodeType.package, + children: [ + MultiChildNavigationNodeData( + name: 'First child node', + type: NavigationNodeType.package, + children: const [], + ), + MultiChildNavigationNodeData( + name: 'Second child node', + type: NavigationNodeType.package, + children: [ + MultiChildNavigationNodeData( + name: 'Third child node', + type: NavigationNodeType.package, + children: const [], + ), + MultiChildNavigationNodeData( + name: 'Fourth child node', + type: NavigationNodeType.package, + children: [ + LeafNavigationNodeData( + name: 'Leaf Child', + type: NavigationNodeType.useCase, + ), + ], + ), + ], + ), + ], + ); + + final result = MultiChildNavigationNodeData( + name: 'Parent node', + type: NavigationNodeType.package, + children: [ + MultiChildNavigationNodeData( + name: 'Second child node', + type: NavigationNodeType.package, + children: [ + MultiChildNavigationNodeData( + name: 'Fourth child node', + type: NavigationNodeType.package, + children: [ + LeafNavigationNodeData( + name: 'Leaf Child', + type: NavigationNodeType.useCase, + ), + ], + ), + ], + ), + ], + ); + + expect(data.filterNode('fourth'), equals(result)); + }); + + test('Can filter by Leaf name', () { + final data = MultiChildNavigationNodeData( + name: 'Parent node', + type: NavigationNodeType.component, + children: [ + LeafNavigationNodeData( + name: 'Leaf Child', + type: NavigationNodeType.useCase, + ), + LeafNavigationNodeData( + name: 'Unwanted Child', + type: NavigationNodeType.useCase, + ), + ], + ); + + final result = MultiChildNavigationNodeData( + name: 'Parent node', + type: NavigationNodeType.component, + children: [ + LeafNavigationNodeData( + name: 'Leaf Child', + type: NavigationNodeType.useCase, + ), + ], + ); + + expect(data.filterNode('leaf'), equals(result)); + }); + }); + }); +} diff --git a/packages/widgetbook_core/test/src/navigation_tree/models/navigation_tree_node_data_test.dart b/packages/widgetbook_core/test/src/navigation_tree/models/navigation_tree_node_data_test.dart new file mode 100644 index 000000000..b0c26307d --- /dev/null +++ b/packages/widgetbook_core/test/src/navigation_tree/models/navigation_tree_node_data_test.dart @@ -0,0 +1,70 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:widgetbook_core/src/navigation_tree/enums/enums.dart'; +import 'package:widgetbook_core/src/navigation_tree/models/models.dart'; + +void main() { + group('$NavigationTreeNodeData', () { + test('Supports value equality', () { + final nodeData1 = NavigationTreeNodeData( + name: 'Node', + type: NavigationNodeType.package, + ); + final nodeData2 = NavigationTreeNodeData( + name: 'Node', + type: NavigationNodeType.package, + ); + expect(nodeData1.hashCode, equals(nodeData2.hashCode)); + expect(nodeData1, equals(nodeData2)); + }); + + test('Returns formatted string', () { + final data = NavigationTreeNodeData( + name: 'Node', + type: NavigationNodeType.package, + ); + + expect( + data.toString(), + equals( + 'NavigationTreeNodeData(' + 'id: null, ' + 'name: Node, ' + 'type: NavigationNodeType.package, ' + 'children: [], ' + 'isInitiallyExpanded: true ' + ')', + ), + ); + }); + + test('Can generate path', () { + final node = NavigationTreeNodeData( + name: 'Parent', + type: NavigationNodeType.package, + children: [ + NavigationTreeNodeData( + name: 'First child', + type: NavigationNodeType.package, + children: [ + NavigationTreeNodeData( + name: 'Second child', + type: NavigationNodeType.package, + children: [ + NavigationTreeNodeData( + name: 'Third child', + type: NavigationNodeType.package, + ), + ], + ), + ], + ), + ], + ); + + expect( + node.children[0].children[0].children[0].path, + equals('first-child/second-child/third-child'), + ); + }); + }); +} diff --git a/packages/widgetbook_core/test/src/navigation_tree/widgets/navigation_tree_item_test.dart b/packages/widgetbook_core/test/src/navigation_tree/widgets/navigation_tree_item_test.dart index ba3a1233a..7d8214a62 100644 --- a/packages/widgetbook_core/test/src/navigation_tree/widgets/navigation_tree_item_test.dart +++ b/packages/widgetbook_core/test/src/navigation_tree/widgets/navigation_tree_item_test.dart @@ -11,7 +11,7 @@ void main() { group( '$NavigationTreeItem', () { - const testNode = NavigationTreeNodeData( + final testNode = NavigationTreeNodeData( name: 'Test Node', type: NavigationNodeType.category, ); @@ -36,7 +36,7 @@ void main() { 'more menu icon is initially not rendered', (WidgetTester tester) async { await tester.pumpWidgetWithMaterial( - child: const NavigationTreeItem(data: testNode), + child: NavigationTreeItem(data: testNode), ); final menuIconFinder = find.byWidgetPredicate( @@ -53,7 +53,7 @@ void main() { 'MouseRegion onEnter and onExit callbacks toggle more menu icon', (WidgetTester tester) async { await tester.pumpWidgetWithMaterial( - child: const NavigationTreeItem(data: testNode), + child: NavigationTreeItem(data: testNode), ); final menuIconFinder = find.byWidgetPredicate( @@ -119,7 +119,7 @@ void main() { (WidgetTester tester) async { const level = 2; await tester.pumpWidgetWithMaterial( - child: const NavigationTreeItem( + child: NavigationTreeItem( data: testNode, level: level, ), @@ -139,13 +139,13 @@ void main() { testWidgets( '$ExpanderIcon is rendered for expandable nodes', (WidgetTester tester) async { - const testNode = NavigationTreeNodeData( + final testNode = NavigationTreeNodeData( name: 'Category', type: NavigationNodeType.category, ); await tester.pumpWidgetWithMaterial( - child: const NavigationTreeItem(data: testNode), + child: NavigationTreeItem(data: testNode), ); expect(testNode.isExpandable, isTrue); @@ -157,13 +157,13 @@ void main() { testWidgets( '$ExpanderIcon is not rendered for non expandable nodes', (WidgetTester tester) async { - const testNode = NavigationTreeNodeData( + final testNode = NavigationTreeNodeData( name: 'Use Case', type: NavigationNodeType.useCase, ); await tester.pumpWidgetWithMaterial( - child: const NavigationTreeItem(data: testNode), + child: NavigationTreeItem(data: testNode), ); expect(testNode.isExpandable, isFalse); @@ -175,13 +175,13 @@ void main() { testWidgets( 'renders $ComponentIcon for component navigation node type', (WidgetTester tester) async { - const testNode = NavigationTreeNodeData( + final testNode = NavigationTreeNodeData( name: 'Component', type: NavigationNodeType.component, ); await tester.pumpWidgetWithMaterial( - child: const NavigationTreeItem(data: testNode), + child: NavigationTreeItem(data: testNode), ); final finder = find.byType(ComponentIcon); @@ -192,13 +192,13 @@ void main() { testWidgets( 'renders $UseCaseIcon for use case navigation node type', (WidgetTester tester) async { - const testNode = NavigationTreeNodeData( + final testNode = NavigationTreeNodeData( name: 'Use Case', type: NavigationNodeType.useCase, ); await tester.pumpWidgetWithMaterial( - child: const NavigationTreeItem(data: testNode), + child: NavigationTreeItem(data: testNode), ); final finder = find.byType(UseCaseIcon); @@ -209,7 +209,7 @@ void main() { testWidgets( 'renders $UseCaseIcon for use case navigation node type', (WidgetTester tester) async { - const testNode = NavigationTreeNodeData( + final testNode = NavigationTreeNodeData( name: 'Use Case', type: NavigationNodeType.useCase, ); @@ -217,7 +217,7 @@ void main() { expect(testNode.isSelectable, isTrue); await tester.pumpWidgetWithMaterial( - child: const NavigationTreeItem( + child: NavigationTreeItem( data: testNode, isSelected: true, ), diff --git a/packages/widgetbook_core/test/src/navigation_tree/widgets/navigation_tree_node_test.dart b/packages/widgetbook_core/test/src/navigation_tree/widgets/navigation_tree_node_test.dart index d835ea7ce..2c9af4ea2 100644 --- a/packages/widgetbook_core/test/src/navigation_tree/widgets/navigation_tree_node_test.dart +++ b/packages/widgetbook_core/test/src/navigation_tree/widgets/navigation_tree_node_test.dart @@ -7,9 +7,10 @@ import '../../../helper/callback_mock.dart'; import '../../../helper/widget_tester_extension.dart'; void main() { - const nodeWithOneLevelOfChildren = NavigationTreeNodeData( + final nodeWithOneLevelOfChildren = NavigationTreeNodeData( name: 'Component', type: NavigationNodeType.component, + isInitiallyExpanded: false, children: [ NavigationTreeNodeData( id: 'use_case_1_id', @@ -29,7 +30,7 @@ void main() { 'Can render correct number of first level child node widgets', (WidgetTester tester) async { await tester.pumpWidgetWithMaterial( - child: const NavigationTreeNode( + child: NavigationTreeNode( data: nodeWithOneLevelOfChildren, ), ); @@ -44,7 +45,7 @@ void main() { testWidgets( 'Calls onNodeSelected with selected node id', (WidgetTester tester) async { - const useCaseNode = NavigationTreeNodeData( + final useCaseNode = NavigationTreeNodeData( id: 'use_case_id', name: 'Use Case', type: NavigationNodeType.useCase, @@ -68,9 +69,8 @@ void main() { 'Can expand children ListView', (WidgetTester tester) async { await tester.pumpWidgetWithMaterial( - child: const NavigationTreeNode( + child: NavigationTreeNode( data: nodeWithOneLevelOfChildren, - initiallyExpanded: false, ), ); diff --git a/packages/widgetbook_core/test/src/navigation_tree/widgets/navigation_tree_test.dart b/packages/widgetbook_core/test/src/navigation_tree/widgets/navigation_tree_test.dart index add8d1607..8b4c9bca3 100644 --- a/packages/widgetbook_core/test/src/navigation_tree/widgets/navigation_tree_test.dart +++ b/packages/widgetbook_core/test/src/navigation_tree/widgets/navigation_tree_test.dart @@ -11,7 +11,7 @@ void main() { testWidgets( 'Calls onNodeSelected with selected node id', (WidgetTester tester) async { - const testTree = [ + final testTree = [ NavigationTreeNodeData( id: 'use_case_id_1', name: 'Use Case', diff --git a/widgetbook_for_widgetbook/lib/navigation/navigation_test_data.dart b/widgetbook_for_widgetbook/lib/navigation/navigation_test_data.dart index 25eb0a4e6..6d769ee48 100644 --- a/widgetbook_for_widgetbook/lib/navigation/navigation_test_data.dart +++ b/widgetbook_for_widgetbook/lib/navigation/navigation_test_data.dart @@ -1,6 +1,6 @@ import 'package:widgetbook_core/widgetbook_core.dart'; -const testNode1 = NavigationTreeNodeData( +final testNode1 = NavigationTreeNodeData( name: 'Component', type: NavigationNodeType.component, children: [ @@ -17,7 +17,7 @@ const testNode1 = NavigationTreeNodeData( ], ); -const testNode2 = NavigationTreeNodeData( +final testNode2 = NavigationTreeNodeData( name: 'Component', type: NavigationNodeType.component, children: [ @@ -29,7 +29,7 @@ const testNode2 = NavigationTreeNodeData( ], ); -const testNode3 = NavigationTreeNodeData( +final testNode3 = NavigationTreeNodeData( name: 'Category', type: NavigationNodeType.category, children: [ @@ -38,7 +38,7 @@ const testNode3 = NavigationTreeNodeData( ], ); -const testNode4 = NavigationTreeNodeData( +final testNode4 = NavigationTreeNodeData( name: 'Component', type: NavigationNodeType.component, children: [ @@ -55,7 +55,7 @@ const testNode4 = NavigationTreeNodeData( ], ); -const testNode5 = NavigationTreeNodeData( +final testNode5 = NavigationTreeNodeData( name: 'Package', type: NavigationNodeType.package, children: [ @@ -64,7 +64,7 @@ const testNode5 = NavigationTreeNodeData( ], ); -const testNode6 = NavigationTreeNodeData( +final testNode6 = NavigationTreeNodeData( name: 'Package', type: NavigationNodeType.package, children: [ diff --git a/widgetbook_for_widgetbook/lib/navigation/navigation_tree.dart b/widgetbook_for_widgetbook/lib/navigation/navigation_tree.dart index c2a1ffea7..0e5c4fd54 100644 --- a/widgetbook_for_widgetbook/lib/navigation/navigation_tree.dart +++ b/widgetbook_for_widgetbook/lib/navigation/navigation_tree.dart @@ -5,7 +5,29 @@ import 'package:widgetbook_for_widgetbook/navigation/navigation_test_data.dart'; @WidgetbookUseCase(name: 'Default', type: NavigationTree) Widget navigationTreeDefaultUseCase(BuildContext context) { - return NavigationTree( - nodes: testTree, - ); + return const NavigationTreeWrapper(); +} + +class NavigationTreeWrapper extends StatefulWidget { + const NavigationTreeWrapper({super.key}); + + @override + State createState() => _NavigationTreeWrapperState(); +} + +class _NavigationTreeWrapperState extends State { + NavigationTreeNodeData? selectedNode; + + @override + Widget build(BuildContext context) { + return NavigationTree( + nodes: testTree, + selectedNode: selectedNode, + onNodeSelected: (node) { + setState(() { + selectedNode = node; + }); + }, + ); + } } diff --git a/widgetbook_for_widgetbook/lib/navigation/navigation_tree_node.dart b/widgetbook_for_widgetbook/lib/navigation/navigation_tree_node.dart index b5c320e38..ffb260263 100644 --- a/widgetbook_for_widgetbook/lib/navigation/navigation_tree_node.dart +++ b/widgetbook_for_widgetbook/lib/navigation/navigation_tree_node.dart @@ -5,7 +5,7 @@ import 'package:widgetbook_for_widgetbook/navigation/navigation_test_data.dart'; @WidgetbookUseCase(name: 'Default', type: NavigationTreeNode) Widget navigationTreeNodeDefaultUseCase(BuildContext context) { - return const NavigationTreeNode( + return NavigationTreeNode( data: testNode5, ); }