diff --git a/example/widgetbook/pages/components/progress_widgetbook.dart b/example/widgetbook/pages/components/progress_widgetbook.dart index 2a9166ac..3dfcbb83 100644 --- a/example/widgetbook/pages/components/progress_widgetbook.dart +++ b/example/widgetbook/pages/components/progress_widgetbook.dart @@ -25,12 +25,8 @@ Widget progressBarUseCase(BuildContext context) => WidgetbookTestWidget( ); Widget progressCircleUseCase(BuildContext context) => - WidgetbookTestWidget(widget: LayoutBuilder(builder: (context, constraints) { - return SizedBox( - width: constraints.maxWidth - ZetaSpacing.xl, - height: constraints.maxHeight, - child: Center( - child: ZetaProgressCircle( + WidgetbookTestWidget(widget: + ZetaProgressCircle( progress: context.knobs.double .slider(label: 'Progress', min: 0, max: 1, initialValue: 0.5) .toDouble(), @@ -41,6 +37,4 @@ Widget progressCircleUseCase(BuildContext context) => options: ZetaCircleSizes.values, labelBuilder: (value) => value.name), ), - ), - ); - })); + ); diff --git a/lib/src/components/buttons/button_group.dart b/lib/src/components/buttons/button_group.dart new file mode 100644 index 00000000..232ce60d --- /dev/null +++ b/lib/src/components/buttons/button_group.dart @@ -0,0 +1,266 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../../zeta_flutter.dart'; + +/// Zeta Button Group +class ZetaButtonGroup extends StatelessWidget { + /// Constructs [ZetaButtonGroup] from a list of [GroupButton]s + const ZetaButtonGroup( + {super.key, + required this.buttons, + required this.rounded, + required this.isLarge,}); + + /// Determines size of [GroupButton] + final bool isLarge; + + /// Determinses border radius of [GroupButton] + final bool rounded; + + /// [GroupButton]s to be rendered in list + final List buttons; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: getButtons(), + ); + } + + /// Returns [GroupButton]s with there appropriate styling. + List getButtons() { + for (final element in buttons) { + element + .._isInitial = element._isFinal = false + .._isLarge = isLarge + .._rounded = rounded; + } + + buttons.first._isInitial = true; + buttons.last._isFinal = true; + + return buttons; + } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('isLarge', isLarge)) + ..add(DiagnosticsProperty('rounded', rounded)) + ; + } +} + +/// Group Button item +// ignore: must_be_immutable +class GroupButton extends StatefulWidget { + /// Constructs [GroupButton] + GroupButton({ + super.key, + this.label, + this.icon, + this.onPress, + }); + + /// Constructs dropdown group button + GroupButton.dropdown({ + super.key, + required this.onPress, + this.icon, + this.label, + }); + + ///Constructs group button with icon + GroupButton.icon({ + super.key, + required this.icon, + this.onPress, + this.label, + }); + + /// Label for [GroupButton] + final String? label; + + /// Optional icon for [GroupButton] + final IconData? icon; + + /// Function for when [GroupButton] is clicked. + final VoidCallback? onPress; + + ///If [GroupButton] is large + bool _isLarge = false; + + ///If [GroupButton] is rounded + bool _rounded = false; + + /// If [GroupButton] is the first button in its list. + bool _isInitial = false; + + /// If [GroupButton] is the final button in its list. + bool _isFinal = false; + + @override + State createState() => _GroupButtonState(); + + /// Returns copy of [GroupButton] with fields. + GroupButton copyWith({bool? isFinal, bool? isInitial}) { + return GroupButton( + key: key, + label: label, + icon: icon, + onPress: onPress, + // isFinal: isFinal ?? this.isFinal, + // isInitial: isInitial ?? this.isInitial, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(StringProperty('Label', label)) + ..add(DiagnosticsProperty('icon', icon)) + ..add(ObjectFlagProperty.has('onPress', onPress)); + } +} + +class _GroupButtonState extends State { + late bool selected; + late MaterialStatesController controller; + + @override + void initState() { + super.initState(); + selected = false; + controller = MaterialStatesController(); + } + + void onPress() { + widget.onPress!(); + setState(() { + selected = !selected; + }); + } + + @override + Widget build(BuildContext context) { + final colors = Zeta.of(context).colors; + + final borderType = + widget._rounded ? ZetaWidgetBorder.rounded : ZetaWidgetBorder.sharp; + + final BorderSide borderSide = + _getBorderSide(controller.value, colors, false); + + return Container( + decoration: BoxDecoration( + border: Border( + top: borderSide, + left: borderSide, + bottom: borderSide, + right: (widget._isFinal) ? borderSide : BorderSide.none, + ), + borderRadius: _getRadius(borderType), + ), + padding: EdgeInsets.zero, + child: FilledButton( + statesController: controller, + onPressed: onPress, + style: getStyle(borderType, colors), + child: SelectionContainer.disabled( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.icon != null) Icon(widget.icon), + Text(widget.label!), + if (widget.onPress != null) + const Icon(ZetaIcons.expand_more_round), + ], + ).paddingAll(_padding), + ), + ), + ); + } + + double get _padding => widget._isLarge ? ZetaSpacing.x4 : ZetaSpacing.x3; + + BorderSide _getBorderSide( + Set states, + ZetaColors colors, + bool finalButton, + ) { + if (states.contains(MaterialState.disabled)) { + return BorderSide(color: colors.cool.shade40); + } + if (states.contains(MaterialState.focused)) { + return BorderSide(color: colors.blue, width: ZetaSpacing.x0_5); + } + return BorderSide( + color: finalButton ? colors.borderDefault : colors.borderSubtle, + ); + } + + BorderRadius _getRadius(ZetaWidgetBorder borderType) { + if (widget._isInitial) { + return borderType.radius.copyWith( + topRight: Radius.zero, + bottomRight: Radius.zero, + ); + } + if (widget._isFinal) { + return borderType.radius.copyWith( + topLeft: Radius.zero, + bottomLeft: Radius.zero, + ); + } + return ZetaRadius.none; + } + + ButtonStyle getStyle(ZetaWidgetBorder borderType, ZetaColors colors) { + final ZetaColorSwatch color = + selected ? colors.cool : ZetaColorSwatch.fromColor(colors.black); + + return ButtonStyle( + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: borderType.radius, + ), + ), + backgroundColor: MaterialStateProperty.resolveWith((states) { + if (selected) return colors.black; + + if (states.contains(MaterialState.disabled)) { + return colors.surfaceDisabled; + } + if (states.contains(MaterialState.pressed)) { + return colors.primary.shade10; + } + if (states.contains(MaterialState.hovered)) { + return colors.cool.shade20; + } + return colors.white; + }), + foregroundColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.disabled)) { + return colors.textDisabled; + } + if (selected) return color.onColor; + return colors.textDefault; + }), + elevation: const MaterialStatePropertyAll(0), + padding: MaterialStateProperty.all(EdgeInsets.zero), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('selected', selected)) + ..add( + DiagnosticsProperty('controller', controller), + ); + } +} diff --git a/lib/src/components/progress/progress_circle.dart b/lib/src/components/progress/progress_circle.dart index 28b546c1..9934a477 100644 --- a/lib/src/components/progress/progress_circle.dart +++ b/lib/src/components/progress/progress_circle.dart @@ -36,7 +36,7 @@ class ZetaProgressCircle extends ZetaProgress { ///Size of [ZetaProgressCircle] final ZetaCircleSizes size; - /// Border Type for [ZetaWidgetBorder] + ///{@macro zeta-component-rounded} final bool rounded; @override @@ -56,17 +56,21 @@ class ZetaProgressCircle extends ZetaProgress { class ZetaProgressCircleState extends ZetaProgressState { @override Widget build(BuildContext context) { - return AnimatedBuilder( - animation: controller, - builder: (context, child) { - return CustomPaint( - size: _getSize(), - painter: CirclePainter( - progress: animation.value, - rounded: widget.rounded, - ), - ); - }, + return ConstrainedBox( + constraints: BoxConstraints.tight(_getSize()), + child: AnimatedBuilder( + animation: controller, + builder: (context, child) { + return CustomPaint( + size: _getSize(), + painter: CirclePainter( + progress: animation.value, + rounded: widget.rounded, + colors: Zeta.of(context).colors + ), + ); + }, + ), ); } @@ -98,7 +102,7 @@ class ZetaProgressCircleState extends ZetaProgressState { ///Class definition for [CirclePainter] class CirclePainter extends CustomPainter { ///Constructor for [CirclePainter] - CirclePainter({this.progress = 0, this.rounded = true}); + CirclePainter({this.progress = 0, this.rounded = true,required this.colors}); ///Percentage of progress in decimal value, defaults to 0 final double progress; @@ -106,14 +110,18 @@ class CirclePainter extends CustomPainter { ///Is circle rounded, defaults to true final bool rounded; + /// ZetaColors + final ZetaColors colors; + final _paint = Paint() - ..color = Colors.blue ..strokeWidth = 4 ..style = PaintingStyle.stroke; @override void paint(Canvas canvas, Size size) { if (rounded) _paint.strokeCap = StrokeCap.round; + _paint.color = colors.primary; + const double fullCircle = 2 * math.pi;