diff --git a/example/lib/home.dart b/example/lib/home.dart index da7d414b..79051b0f 100644 --- a/example/lib/home.dart +++ b/example/lib/home.dart @@ -5,6 +5,7 @@ import 'package:zeta_example/pages/components/avatar_example.dart'; import 'package:zeta_example/pages/components/badges_example.dart'; import 'package:zeta_example/pages/components/banner_example.dart'; import 'package:zeta_example/pages/components/bottom_sheet_example.dart'; +import 'package:zeta_example/pages/components/breadcrumbs_example.dart'; import 'package:zeta_example/pages/components/button_example.dart'; import 'package:zeta_example/pages/components/checkbox_example.dart'; import 'package:zeta_example/pages/components/chip_example.dart'; @@ -33,11 +34,14 @@ final List components = [ Component(BannerExample.name, (context) => const BannerExample()), Component(BadgesExample.name, (context) => const BadgesExample()), Component(BottomSheetExample.name, (context) => const BottomSheetExample()), + Component(BreadCrumbsExample.name, (context) => const BreadCrumbsExample()), Component(ButtonExample.name, (context) => const ButtonExample()), Component(CheckBoxExample.name, (context) => const CheckBoxExample()), Component(ChipExample.name, (context) => const ChipExample()), - Component(NavigationBarExample.name, (context) => const NavigationBarExample()), - Component(PasswordInputExample.name, (context) => const PasswordInputExample()), + Component( + NavigationBarExample.name, (context) => const NavigationBarExample()), + Component( + PasswordInputExample.name, (context) => const PasswordInputExample()), Component(ProgressExample.name, (context) => const ProgressExample()), Component(DialPadExample.name, (context) => const DialPadExample()), ]; @@ -104,21 +108,27 @@ class _HomeState extends State { title: Text('Widgets'), backgroundColor: Zeta.of(context).colors.warm.shade30, children: _components - .map((item) => ListTile(title: Text(item.name), onTap: () => context.go('/${item.name}'))) + .map((item) => ListTile( + title: Text(item.name), + onTap: () => context.go('/${item.name}'))) .toList(), ), ExpansionTile( title: Text('Theme'), backgroundColor: Zeta.of(context).colors.warm.shade30, children: _theme - .map((item) => ListTile(title: Text(item.name), onTap: () => context.go('/${item.name}'))) + .map((item) => ListTile( + title: Text(item.name), + onTap: () => context.go('/${item.name}'))) .toList(), ), ExpansionTile( title: Text('Assets'), backgroundColor: Zeta.of(context).colors.warm.shade30, children: _assets - .map((item) => ListTile(title: Text(item.name), onTap: () => context.go('/${item.name}'))) + .map((item) => ListTile( + title: Text(item.name), + onTap: () => context.go('/${item.name}'))) .toList(), ), ], diff --git a/example/lib/pages/components/breadcrumbs_example.dart b/example/lib/pages/components/breadcrumbs_example.dart new file mode 100644 index 00000000..9bc02348 --- /dev/null +++ b/example/lib/pages/components/breadcrumbs_example.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +class BreadCrumbsExample extends StatefulWidget { + static const String name = 'Breadcrumbs'; + + const BreadCrumbsExample({super.key}); + + @override + State createState() => _BreadCrumbsExampleState(); +} + +class _BreadCrumbsExampleState extends State { + List _children = [ + ZetaBreadCrumb( + label: 'Icon before with seperator', + onPressed: () { + print("Breadcrumb " + 0.toString() + "Clicked"); + }, + ), + ]; + int index = 1; + + @override + Widget build(BuildContext context) { + return ExampleScaffold( + name: 'Breadcrumbs', + child: Center( + child: SingleChildScrollView( + child: SizedBox( + width: double.infinity, + child: Column(children: [ + ZetaBreadCrumbs(children: _children), + SizedBox( + height: 50, + ), + FilledButton( + onPressed: () { + setState(() { + _children.add( + ZetaBreadCrumb( + label: 'Icon before with seperator', + onPressed: () { + print("Breadcrumb clicked"); + }, + ), + ); + index++; + }); + }, + child: Text("Add Breadcrumb")) + ])), + ), + ), + ); + } +} diff --git a/example/lib/pages/components/progress_example.dart b/example/lib/pages/components/progress_example.dart index 5bfc6642..3fe929b1 100644 --- a/example/lib/pages/components/progress_example.dart +++ b/example/lib/pages/components/progress_example.dart @@ -51,12 +51,19 @@ class ProgressExampleState extends State { isThin: false, label: "UPLOADING ...", ), + SizedBox( + height: 40, + ), Wrapper( stepsCompleted: 0, circleSize: ZetaCircleSizes.xl, rounded: false, isCircle: true, ), + SizedBox( + height: 40, + ), + Row(mainAxisAlignment: MainAxisAlignment.center, children: []) ]), ), ), diff --git a/example/widgetbook/main.dart b/example/widgetbook/main.dart index 8abeaece..b08e0c0c 100644 --- a/example/widgetbook/main.dart +++ b/example/widgetbook/main.dart @@ -8,6 +8,7 @@ import 'pages/components/avatar_widgetbook.dart'; import 'pages/components/badges_widgetbook.dart'; import 'pages/components/banner_widgetbook.dart'; import 'pages/components/bottom_sheet_widgetbook.dart'; +import 'pages/components/breadcrumbs_widgetbook.dart'; import 'pages/components/button_widgetbook.dart'; import 'pages/components/checkbox_widgetbook.dart'; import 'pages/components/chip_widgetbook.dart'; @@ -39,47 +40,92 @@ class HotReload extends StatelessWidget { WidgetbookComponent( name: 'Badge', useCases: [ - WidgetbookUseCase(name: 'Status Label', builder: (context) => statusLabelUseCase(context)), - WidgetbookUseCase(name: 'Priority Pill', builder: (context) => priorityPillUseCase(context)), - WidgetbookUseCase(name: 'Badge', builder: (context) => badgeUseCase(context)), - WidgetbookUseCase(name: 'Indicators', builder: (context) => indicatorsUseCase(context)), - WidgetbookUseCase(name: 'Tags', builder: (context) => tagsUseCase(context)), WidgetbookUseCase( - name: 'Workcloud Indicators', builder: (context) => workcloudIndicatorsUseCase(context)), + name: 'Status Label', + builder: (context) => statusLabelUseCase(context)), + WidgetbookUseCase( + name: 'Priority Pill', + builder: (context) => priorityPillUseCase(context)), + WidgetbookUseCase( + name: 'Badge', builder: (context) => badgeUseCase(context)), + WidgetbookUseCase( + name: 'Indicators', + builder: (context) => indicatorsUseCase(context)), + WidgetbookUseCase( + name: 'Tags', builder: (context) => tagsUseCase(context)), + WidgetbookUseCase( + name: 'Workcloud Indicators', + builder: (context) => workcloudIndicatorsUseCase(context)), ], ), - WidgetbookUseCase(name: 'Avatar', builder: (context) => avatarUseCase(context)), - WidgetbookUseCase(name: 'Checkbox', builder: (context) => checkboxUseCase(context)), + WidgetbookUseCase( + name: 'Avatar', builder: (context) => avatarUseCase(context)), + WidgetbookUseCase( + name: 'Checkbox', + builder: (context) => checkboxUseCase(context)), WidgetbookComponent( name: 'Buttons', useCases: [ - WidgetbookUseCase(name: 'Button', builder: (context) => buttonUseCase(context)), - WidgetbookUseCase(name: 'Icon Button', builder: (context) => iconButtonUseCase(context)), WidgetbookUseCase( - name: 'Floating Action Button', builder: (context) => floatingActionButtonUseCase(context)), - WidgetbookUseCase(name: 'Group Button', builder: (context) => buttonGroupUseCase(context)), + name: 'Button', + builder: (context) => buttonUseCase(context)), + WidgetbookUseCase( + name: 'Icon Button', + builder: (context) => iconButtonUseCase(context)), + WidgetbookUseCase( + name: 'Floating Action Button', + builder: (context) => floatingActionButtonUseCase(context)), + WidgetbookUseCase( + name: 'Group Button', + builder: (context) => buttonGroupUseCase(context)), ], ), - WidgetbookUseCase(name: 'Banners', builder: (context) => bannerUseCase(context)), - WidgetbookUseCase(name: 'In Page Banners', builder: (context) => inPageBannerUseCase(context)), - WidgetbookUseCase(name: 'Accordion', builder: (context) => accordionUseCase(context)), + WidgetbookUseCase( + name: 'BreadCrumbs', + builder: (context) => breadCrumbsUseCase(context)), + WidgetbookUseCase( + name: 'Banners', builder: (context) => bannerUseCase(context)), + WidgetbookUseCase( + name: 'In Page Banners', + builder: (context) => inPageBannerUseCase(context)), + WidgetbookUseCase( + name: 'Accordion', + builder: (context) => accordionUseCase(context)), WidgetbookComponent( name: 'Chips', useCases: [ - WidgetbookUseCase(name: 'Filter Chip', builder: (context) => filterChipUseCase(context)), - WidgetbookUseCase(name: 'Input Chip', builder: (context) => inputChipUseCase(context)), - WidgetbookUseCase(name: 'Assist Chip', builder: (context) => assistChipUseCase(context)), + WidgetbookUseCase( + name: 'Filter Chip', + builder: (context) => filterChipUseCase(context)), + WidgetbookUseCase( + name: 'Input Chip', + builder: (context) => inputChipUseCase(context)), + WidgetbookUseCase( + name: 'Assist Chip', + builder: (context) => assistChipUseCase(context)), ], ), - WidgetbookUseCase(name: 'Password Input', builder: (context) => passwordInputUseCase(context)), - WidgetbookUseCase(name: 'Bottom Sheet', builder: (context) => bottomSheetContentUseCase(context)), - WidgetbookUseCase(name: 'Dial Pad', builder: (context) => dialPadUseCase(context)), - WidgetbookUseCase(name: 'Navigation Bar', builder: (context) => navigationBarUseCase(context)), + WidgetbookUseCase( + name: 'Password Input', + builder: (context) => passwordInputUseCase(context)), + WidgetbookUseCase( + name: 'Content', + builder: (context) => bottomSheetContentUseCase(context)), + WidgetbookUseCase( + name: 'Dial Pad', + builder: (context) => dialPadUseCase(context)), + WidgetbookUseCase( + name: 'Navigation Bar', + builder: (context) => navigationBarUseCase(context)), WidgetbookComponent( name: 'Progress', useCases: [ - WidgetbookUseCase(name: 'Bar', builder: (context) => progressBarUseCase(context)), - WidgetbookUseCase(name: 'Circle', builder: (context) => progressCircleUseCase(context)) + WidgetbookUseCase( + name: 'Bar', + builder: (context) => progressBarUseCase(context)), + WidgetbookUseCase( + name: 'Circle', + builder: (context) => progressCircleUseCase(context)) ], ), ]..sort((a, b) => a.name.compareTo(b.name)), @@ -88,17 +134,23 @@ class HotReload extends StatelessWidget { name: 'Theme', isInitiallyExpanded: false, children: [ - WidgetbookUseCase(name: 'Typography', builder: (context) => typographyUseCase(context)), - WidgetbookUseCase(name: 'Color', builder: (context) => colorUseCase(context)), - WidgetbookUseCase(name: 'Spacing', builder: (context) => spacingUseCase(context)), - WidgetbookUseCase(name: 'Radius', builder: (context) => radiusUseCase(context)), + WidgetbookUseCase( + name: 'Typography', + builder: (context) => typographyUseCase(context)), + WidgetbookUseCase( + name: 'Color', builder: (context) => colorUseCase(context)), + WidgetbookUseCase( + name: 'Spacing', builder: (context) => spacingUseCase(context)), + WidgetbookUseCase( + name: 'Radius', builder: (context) => radiusUseCase(context)), ]..sort((a, b) => a.name.compareTo(b.name)), ), WidgetbookCategory( name: 'Assets', isInitiallyExpanded: false, children: [ - WidgetbookUseCase(name: 'Icons', builder: (context) => iconsUseCase(context)), + WidgetbookUseCase( + name: 'Icons', builder: (context) => iconsUseCase(context)), ]..sort((a, b) => a.name.compareTo(b.name)), ), ], @@ -115,16 +167,23 @@ class HotReload extends StatelessWidget { ), ThemeAddon( themes: [ - WidgetbookTheme(name: 'Light Mode', data: _Theme(isDark: false, isAAA: false)), - WidgetbookTheme(name: 'Dark Mode', data: _Theme(isDark: true, isAAA: false)), - WidgetbookTheme(name: 'Light Mode AAA', data: _Theme(isDark: false, isAAA: true)), - WidgetbookTheme(name: 'Dark Mode AAA', data: _Theme(isDark: true, isAAA: true)), + WidgetbookTheme( + name: 'Light Mode', data: _Theme(isDark: false, isAAA: false)), + WidgetbookTheme( + name: 'Dark Mode', data: _Theme(isDark: true, isAAA: false)), + WidgetbookTheme( + name: 'Light Mode AAA', + data: _Theme(isDark: false, isAAA: true)), + WidgetbookTheme( + name: 'Dark Mode AAA', data: _Theme(isDark: true, isAAA: true)), ], themeBuilder: (context, theme, child) { _Theme _theme = theme; return ZetaProvider( - initialContrast: _theme.isAAA ? ZetaContrast.aaa : ZetaContrast.aa, - initialThemeMode: _theme.isDark ? ThemeMode.dark : ThemeMode.light, + initialContrast: + _theme.isAAA ? ZetaContrast.aaa : ZetaContrast.aa, + initialThemeMode: + _theme.isDark ? ThemeMode.dark : ThemeMode.light, builder: (context, theme, themeMode) { return Builder( builder: (context) { diff --git a/example/widgetbook/pages/components/breadcrumbs_widgetbook.dart b/example/widgetbook/pages/components/breadcrumbs_widgetbook.dart new file mode 100644 index 00000000..e36c7f8b --- /dev/null +++ b/example/widgetbook/pages/components/breadcrumbs_widgetbook.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../test/test_components.dart'; +import '../../utils/utils.dart'; + +Widget breadCrumbsUseCase(BuildContext context) => WidgetbookTestWidget( + widget: Center( + child: BreadCrumbExample(context), + )); + +class BreadCrumbExample extends StatefulWidget { + const BreadCrumbExample(this.c); + final BuildContext c; + + @override + State createState() => _BreadCrumbExampleState(); +} + +class _BreadCrumbExampleState extends State { + List _children = [ + ZetaBreadCrumb( + label: 'Icon before with seperator', + onPressed: () { + print("Breadcrumb " + 0.toString() + "Clicked"); + }, + ), + ]; + int index = 1; + + @override + Widget build(BuildContext _) { + return SingleChildScrollView( + child: SizedBox( + width: double.infinity, + child: Column(children: [ + ZetaBreadCrumbs( + children: _children, + rounded: widget.c.knobs.boolean(label: 'Rounded'), + activeIcon: iconKnob(context), + ), + SizedBox( + height: 50, + ), + FilledButton( + onPressed: () { + setState(() { + _children.add( + ZetaBreadCrumb( + label: 'Icon before with seperator', + onPressed: () { + print("Breadcrumb clicked"); + }, + ), + ); + index++; + }); + }, + child: Text("Add Breadcrumb")) + ])), + ); + } +} diff --git a/lib/src/components/breadcrumbs/breadcrumbs.dart b/lib/src/components/breadcrumbs/breadcrumbs.dart new file mode 100644 index 00000000..cb791273 --- /dev/null +++ b/lib/src/components/breadcrumbs/breadcrumbs.dart @@ -0,0 +1,352 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../../zeta_flutter.dart'; + +///Class for [ZetaBreadCrumbs] +class ZetaBreadCrumbs extends StatefulWidget { + ///Constructor for [ZetaBreadCrumbs] + const ZetaBreadCrumbs({ + super.key, + required this.children, + this.rounded = true, + this.activeIcon, + }); + + /// Breadcrumb children + final List children; + + /// {@macro zeta-component-rounded} + final bool rounded; + + /// Active icon for breadcrumb + final IconData? activeIcon; + + @override + State createState() => _ZetaBreadCrumbsState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(IterableProperty('children', children)) + ..add(DiagnosticsProperty('rounded', rounded)) + ..add(DiagnosticsProperty('activeIcon', activeIcon)); + } +} + +class _ZetaBreadCrumbsState extends State { + late int _selectedIndex; + late List _children; + + @override + void initState() { + super.initState(); + _selectedIndex = widget.children.length - 1; + _children = [...widget.children]; + } + + @override + void didUpdateWidget(ZetaBreadCrumbs oldWidget) { + if (widget.children.length != _children.length) { + setState(() { + _selectedIndex = widget.children.length - 1; + _children = [...widget.children]; + }); + } + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: renderedChildren(widget.children) + .divide( + const Row( + children: [ + SizedBox( + width: ZetaSpacing.xs, + ), + Icon( + ZetaIcons.chevron_right_round, + size: ZetaSpacing.x5, + ), + SizedBox( + width: ZetaSpacing.xs, + ), + ], + ), + ) + .toList(), + ), + ); + } + + ///Creates breadcumb widget + ZetaBreadCrumb createBreadCrumb(ZetaBreadCrumb input, int index) { + return ZetaBreadCrumb( + label: input.label, + isSelected: _selectedIndex == index, + onPressed: () { + setState(() { + _selectedIndex = index; + }); + input.onPressed.call(); + }, + activeIcon: widget.activeIcon, + ); + } + + List renderedChildren(List children) { + final List returnList = []; + if (children.length > 3) { + returnList.add(createBreadCrumb(children.first, 0)); + + final List truncatedChildren = []; + + for (final (index, element) + in children.sublist(1, children.length - 1).indexed) { + truncatedChildren.add(createBreadCrumb(element, index + 1)); + } + returnList + ..add( + BreadCrumbsTruncated( + rounded: widget.rounded, + children: truncatedChildren, + ), + ) + ..add(createBreadCrumb(children.last, children.length - 1)); + } else { + for (final (index, element) in children.indexed) { + returnList.add(createBreadCrumb(element, index)); + } + } + return returnList; + } +} + +/// Class for untruncated [ZetaBreadCrumb]. +class ZetaBreadCrumb extends StatefulWidget { + ///Constructor for [ZetaBreadCrumb] + const ZetaBreadCrumb({ + super.key, + required this.label, + this.icon, + this.isSelected = false, + required this.onPressed, + this.activeIcon, + }); + + /// [ZetaBreadCrumb] label. + final String label; + + /// Selected icon. + final IconData? icon; + + /// Is [ZetaBreadCrumb] selected. + final bool isSelected; + + /// Handles press for [ZetaBreadCrumb] + final VoidCallback onPressed; + + /// Active icon for [ZetaBreadCrumb] + final IconData? activeIcon; + + @override + State createState() => _ZetaBreadCrumbState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(ObjectFlagProperty.has('onPressed', onPressed)) + ..add(StringProperty('label', label)) + ..add(DiagnosticsProperty('icon', icon)) + ..add(DiagnosticsProperty('isSelected', isSelected)) + ..add(DiagnosticsProperty('activeIcon', activeIcon)); + } +} + +class _ZetaBreadCrumbState extends State { + final controller = MaterialStatesController(); + + @override + void initState() { + super.initState(); + controller.addListener(() { + if (context.mounted && mounted) { + setState(() {}); + } + }); + } + + @override + Widget build(BuildContext context) { + final colors = Zeta.of(context).colors; + return Material( + color: Colors.transparent, + child: InkWell( + statesController: controller, + onTap: widget.onPressed, + enableFeedback: false, + splashColor: Colors.transparent, + overlayColor: MaterialStateProperty.resolveWith((states) { + return Colors.transparent; + }), + child: Row( + children: [ + if (widget.isSelected) + Icon( + widget.activeIcon ?? ZetaIcons.star_round, + color: getColor(controller.value, colors), + ), + const SizedBox( + width: ZetaSpacing.xs, + ), + Text( + widget.label, + style: TextStyle(color: getColor(controller.value, colors)), + ), + ], + ), + ), + ); + } + + /// Get color of breadcrumb based on state. + Color getColor(Set states, ZetaColors colors) { + if (states.contains(MaterialState.hovered)) { + return colors.blue; + } + if (widget.isSelected) return colors.black; + return colors.textSubtle; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add( + DiagnosticsProperty( + 'controller', + controller, + ), + ); + } +} + +///Class for [BreadCrumbsTruncated] +class BreadCrumbsTruncated extends StatefulWidget { + ///Constructor for [BreadCrumbsTruncated] + const BreadCrumbsTruncated({ + super.key, + required this.children, + required this.rounded, + }); + + ///Breadcrumb children + final List children; + + /// {@macro zeta-component-rounded} + final bool rounded; + + @override + State createState() => _BreadCrumbsTruncatedState(); + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('rounded', rounded)); + } +} + +class _BreadCrumbsTruncatedState extends State { + bool _expanded = false; + + @override + Widget build(BuildContext context) { + final colors = Zeta.of(context).colors; + + return _expanded + ? expandedBreadcrumb() + : FilledButton( + onPressed: () { + setState(() { + _expanded = true; + }); + }, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.hovered)) { + return colors.surfaceHovered; + } + if (states.contains(MaterialState.pressed)) { + return colors.primary.shade10; + } + if (states.contains(MaterialState.disabled)) { + return colors.surfaceDisabled; + } + return colors.warm.shade10; + }), + foregroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.disabled)) { + return colors.textDisabled; + } + return colors.textDefault; + }), + shape: MaterialStatePropertyAll( + RoundedRectangleBorder( + borderRadius: + (widget.rounded ? ZetaRadius.minimal : ZetaRadius.none), + ), + ), + side: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.focused)) { + return BorderSide( + width: ZetaSpacing.x0_5, + color: colors.primary.shade100, + ); + } + if (states.isEmpty) { + return BorderSide(color: colors.borderDefault, width: 0.5); + } + return null; + }), + padding: MaterialStateProperty.all(EdgeInsets.zero), + minimumSize: MaterialStateProperty.all(Size.zero), + elevation: const MaterialStatePropertyAll(0), + ), + child: Icon( + widget.rounded + ? ZetaIcons.more_horizontal_round + : ZetaIcons.more_horizontal_sharp, + size: ZetaSpacing.x4, + ) + .paddingHorizontal(ZetaSpacing.xs) + .paddingVertical(ZetaSpacing.xxs), + ); + } + + Widget expandedBreadcrumb() { + return Row( + children: widget.children + .divide( + const Row( + children: [ + SizedBox( + width: ZetaSpacing.xs, + ), + Icon( + ZetaIcons.chevron_right_round, + size: ZetaSpacing.x5, + ), + SizedBox( + width: ZetaSpacing.xs, + ), + ], + ), + ) + .toList(), + ); + } +} diff --git a/lib/zeta_flutter.dart b/lib/zeta_flutter.dart index 9c887252..75d941d8 100644 --- a/lib/zeta_flutter.dart +++ b/lib/zeta_flutter.dart @@ -14,6 +14,7 @@ export 'src/components/banners/in_page_banner.dart'; export 'src/components/banners/system_banner.dart'; export 'src/components/bottom sheets/bottom_sheet.dart'; export 'src/components/bottom sheets/menu_items.dart'; +export 'src/components/breadcrumbs/breadcrumbs.dart'; export 'src/components/buttons/button.dart'; export 'src/components/buttons/button_group.dart'; export 'src/components/buttons/button_style.dart';