diff --git a/forui/example/lib/sandbox.dart b/forui/example/lib/sandbox.dart index 563e49a02..04ef76a49 100644 --- a/forui/example/lib/sandbox.dart +++ b/forui/example/lib/sandbox.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:forui/forui.dart'; -import 'package:forui/src/widgets/accordion.dart'; class Sandbox extends StatefulWidget { const Sandbox({super.key}); @@ -24,15 +23,13 @@ class _SandboxState extends State { children: [ FAccordion( title: 'Is it Accessible?', - childHeight: 100, - initiallyExpanded: false, - onExpanded: () {}, + controller: FAccordionController(initiallyExpanded: false), child: const Text( 'Yes. It adheres to the WAI-ARIA design pattern', textAlign: TextAlign.left, ), ), - SizedBox(height: 20), + const SizedBox(height: 20), FSelectGroup( label: const Text('Select Group'), description: const Text('Select Group Description'), diff --git a/forui/lib/forui.dart b/forui/lib/forui.dart index 62eb08d45..cf08d5ad0 100644 --- a/forui/lib/forui.dart +++ b/forui/lib/forui.dart @@ -5,6 +5,7 @@ export 'assets.dart'; export 'foundation.dart'; export 'theme.dart'; +export 'widgets/accordion.dart'; export 'widgets/alert.dart'; export 'widgets/avatar.dart'; export 'widgets/badge.dart'; diff --git a/forui/lib/src/theme/theme_data.dart b/forui/lib/src/theme/theme_data.dart index d5a63ae4a..486ad9354 100644 --- a/forui/lib/src/theme/theme_data.dart +++ b/forui/lib/src/theme/theme_data.dart @@ -1,6 +1,5 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; -import 'package:forui/src/widgets/accordion.dart'; import 'package:meta/meta.dart'; @@ -138,7 +137,7 @@ final class FThemeData with Diagnosticable { colorScheme: colorScheme, typography: typography, style: style, - accordionStyle: FAccordionStyle.inherit(colorScheme: colorScheme, style: style, typography: typography), + accordionStyle: FAccordionStyle.inherit(colorScheme: colorScheme, typography: typography), alertStyles: FAlertStyles.inherit(colorScheme: colorScheme, typography: typography, style: style), avatarStyle: FAvatarStyle.inherit(colorScheme: colorScheme, typography: typography), badgeStyles: FBadgeStyles.inherit(colorScheme: colorScheme, typography: typography, style: style), @@ -290,7 +289,6 @@ final class FThemeData with Diagnosticable { ..add(DiagnosticsProperty('scaffoldStyle', scaffoldStyle, level: DiagnosticLevel.debug)) ..add(DiagnosticsProperty('selectGroupStyle', selectGroupStyle, level: DiagnosticLevel.debug)) ..add(DiagnosticsProperty('switchStyle', switchStyle, level: DiagnosticLevel.debug)); - } @override diff --git a/forui/lib/src/widgets/accordion.dart b/forui/lib/src/widgets/accordion.dart index ac345627f..58a59ae5c 100644 --- a/forui/lib/src/widgets/accordion.dart +++ b/forui/lib/src/widgets/accordion.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'dart:math' as math; import 'package:forui/forui.dart'; @@ -9,44 +10,78 @@ import 'package:forui/src/foundation/tappable.dart'; import 'package:forui/src/foundation/util.dart'; import 'package:meta/meta.dart'; +/// A controller that stores the expanded state of an [FAccordion]. +class FAccordionController extends ValueNotifier { + bool _expanded; + + /// Creates a [FAccordionController]. + FAccordionController({ + bool? initiallyExpanded, + }) : _expanded = initiallyExpanded ?? true, + super(initiallyExpanded ?? true); + + /// whether the accordion is expanded. + bool get expanded => _expanded; + + /// Toggles the expansion state of the accordion. + bool toggle() { + _expanded = !_expanded; + notifyListeners(); + return _expanded; + } +} + +/// A vertically stacked set of interactive headings that each reveal a section of content. +/// +/// See: +/// * https://forui.dev/docs/accordion for working examples. class FAccordion extends StatefulWidget { - /// The divider's style. Defaults to the appropriate style in [FThemeData.dividerStyles]. + /// The accordion's style. Defaults to the appropriate style in [FThemeData.accordionStyle]. final FAccordionStyle? style; + + /// The title. final String title; - final bool initiallyExpanded; - final VoidCallback onExpanded; - final double childHeight; - final double removeChildAnimationPercentage; + + /// The accordion's controller. + final FAccordionController controller; + + /// The child. final Widget child; + /// Creates an [FAccordion]. const FAccordion({ required this.child, - required this.childHeight, - required this.initiallyExpanded, - required this.onExpanded, + required this.controller, this.title = '', this.style, - this.removeChildAnimationPercentage = 0, super.key, }); @override + //ignore:library_private_types_in_public_api _FAccordionState createState() => _FAccordionState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('style', style)) + ..add(StringProperty('title', title)) + ..add(DiagnosticsProperty('controller', controller)); + } } class _FAccordionState extends State with SingleTickerProviderStateMixin { late Animation animation; late AnimationController controller; - bool _isExpanded = false; bool _hovered = false; @override void initState() { super.initState(); - _isExpanded = widget.initiallyExpanded; controller = AnimationController( duration: const Duration(milliseconds: 500), - value: _isExpanded ? 1.0 : 0.0, + value: widget.controller.expanded ? 1.0 : 0.0, vsync: this, ); animation = Tween( @@ -60,8 +95,6 @@ class _FAccordionState extends State with SingleTickerProviderStateM )..addListener(() { setState(() {}); }); - - _isExpanded ? controller.forward() : controller.reverse(); } @override @@ -73,15 +106,7 @@ class _FAccordionState extends State with SingleTickerProviderStateM onEnter: (_) => setState(() => _hovered = true), onExit: (_) => setState(() => _hovered = false), child: FTappable( - onPress: () { - if (_isExpanded) { - controller.reverse(); - } else { - controller.forward(); - } - setState(() => _isExpanded = !_isExpanded); - widget.onExpanded(); - }, + onPress: () => widget.controller.toggle() ? controller.forward() : controller.reverse(), child: Container( padding: style.titlePadding, child: Row( @@ -102,10 +127,7 @@ class _FAccordionState extends State with SingleTickerProviderStateM ), Transform.rotate( angle: (animation.value / 100 * -180 + 90) * math.pi / 180.0, - child: FAssets.icons.chevronRight( - height: 20, - colorFilter: const ColorFilter.mode(Colors.black, BlendMode.srcIn), - ), + child: style.icon, ), ], ), @@ -126,8 +148,7 @@ class _FAccordionState extends State with SingleTickerProviderStateM ), ), FDivider( - style: context.theme.dividerStyles.horizontal - .copyWith(padding: EdgeInsets.zero, color: context.theme.colorScheme.border), + style: context.theme.dividerStyles.horizontal.copyWith(padding: EdgeInsets.zero, color: style.dividerColor), ), ], ); @@ -138,6 +159,14 @@ class _FAccordionState extends State with SingleTickerProviderStateM controller.dispose(); super.dispose(); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('animation', animation)) + ..add(DiagnosticsProperty('controller', controller)); + } } /// The [FAccordion] styles. @@ -151,16 +180,33 @@ final class FAccordionStyle with Diagnosticable { /// The padding of the content. final EdgeInsets contentPadding; + /// The icon. + final SvgPicture icon; + + /// The divider's color. + final Color dividerColor; + /// Creates a [FAccordionStyle]. - FAccordionStyle({required this.title, required this.titlePadding, required this.contentPadding}); + FAccordionStyle({ + required this.title, + required this.titlePadding, + required this.contentPadding, + required this.icon, + required this.dividerColor, + }); - /// Creates a [FDividerStyles] that inherits its properties from [colorScheme] and [style]. - FAccordionStyle.inherit({required FColorScheme colorScheme, required FStyle style, required FTypography typography}) + /// Creates a [FDividerStyles] that inherits its properties from [colorScheme]. + FAccordionStyle.inherit({required FColorScheme colorScheme, required FTypography typography}) : title = typography.base.copyWith( fontWeight: FontWeight.w500, ), titlePadding = const EdgeInsets.symmetric(vertical: 15), - contentPadding = const EdgeInsets.only(bottom: 15); + contentPadding = const EdgeInsets.only(bottom: 15), + icon = FAssets.icons.chevronRight( + height: 20, + colorFilter: ColorFilter.mode(colorScheme.primary, BlendMode.srcIn), + ), + dividerColor = colorScheme.border; /// Returns a copy of this [FAccordionStyle] with the given properties replaced. @useResult @@ -168,11 +214,15 @@ final class FAccordionStyle with Diagnosticable { TextStyle? title, EdgeInsets? titlePadding, EdgeInsets? contentPadding, + SvgPicture? icon, + Color? dividerColor, }) => FAccordionStyle( title: title ?? this.title, titlePadding: titlePadding ?? this.titlePadding, contentPadding: contentPadding ?? this.contentPadding, + icon: icon ?? this.icon, + dividerColor: dividerColor ?? this.dividerColor, ); @override @@ -181,7 +231,8 @@ final class FAccordionStyle with Diagnosticable { properties ..add(DiagnosticsProperty('title', title)) ..add(DiagnosticsProperty('padding', titlePadding)) - ..add(DiagnosticsProperty('contentPadding', contentPadding)); + ..add(DiagnosticsProperty('contentPadding', contentPadding)) + ..add(ColorProperty('dividerColor', dividerColor)); } @override @@ -191,13 +242,15 @@ final class FAccordionStyle with Diagnosticable { runtimeType == other.runtimeType && title == other.title && titlePadding == other.titlePadding && - contentPadding == other.contentPadding; + contentPadding == other.contentPadding && + icon == other.icon && + dividerColor == other.dividerColor; @override - int get hashCode => title.hashCode ^ titlePadding.hashCode ^ contentPadding.hashCode; + int get hashCode => + title.hashCode ^ titlePadding.hashCode ^ contentPadding.hashCode ^ icon.hashCode ^ dividerColor.hashCode; } - class _Expandable extends SingleChildRenderObjectWidget { final double _percentage; @@ -228,7 +281,6 @@ class _ExpandableBox extends RenderBox with RenderObjectWithChildMixin { diff --git a/forui/lib/widgets/accordion.dart b/forui/lib/widgets/accordion.dart new file mode 100644 index 000000000..a2867fd57 --- /dev/null +++ b/forui/lib/widgets/accordion.dart @@ -0,0 +1,8 @@ +/// {@category Widgets} +/// +/// An image element with a fallback for representing the user. +/// +/// See https://forui.dev/docs/avatar for working examples. +library forui.widgets.accordion; + +export '../src/widgets/accordion.dart';