Skip to content

Commit

Permalink
feat: switch, dividers, progress indicators (#670)
Browse files Browse the repository at this point in the history
* feat: VoicesSwitch widget

* feat: VoicesDivider

* feat: VoicesIndicators

* fix: missing comma

* fix: documentation update, rename divider with name to VoicesTextDivider

* feat: VoicesVerticalDivider

* fix: Remove Switch category annotation
  • Loading branch information
damian-molinski authored Aug 9, 2024
1 parent c420b00 commit e014bfc
Show file tree
Hide file tree
Showing 13 changed files with 513 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';

/// A Voices circular progress indicator with optional track visibility.
///
/// This widget provides a circular progress indicator with customization
/// options for the progress value and track visibility.
class VoicesCircularProgressIndicator extends StatelessWidget {
/// The current progress value, from 0.0 to 1.0. If null,
/// the indicator will be indeterminate.
final double? value;

/// Whether to show the progress indicator's track.
final bool showTrack;

/// Creates a [VoicesCircularProgressIndicator] widget.
const VoicesCircularProgressIndicator({
super.key,
this.value,
this.showTrack = true,
});

@override
Widget build(BuildContext context) {
return CircularProgressIndicator(
value: value,
strokeCap: StrokeCap.round,
backgroundColor: showTrack ? null : Colors.transparent,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';

/// A custom linear progress indicator with optional track visibility and
/// rounded corners.
///
/// This widget provides a linear progress indicator with customization
/// options for the progress value, track visibility, and rounded corners.
class VoicesLinearProgressIndicator extends StatelessWidget {
/// The current progress value, from 0.0 to 1.0. If null, the indicator will
/// be indeterminate.
final double? value;

/// Whether to show the progress indicator's track.
final bool showTrack;

/// Creates a [VoicesLinearProgressIndicator] widget.
const VoicesLinearProgressIndicator({
super.key,
this.value,
this.showTrack = true,
});

@override
Widget build(BuildContext context) {
return LinearProgressIndicator(
value: value,
borderRadius: BorderRadius.circular(4),
backgroundColor: showTrack ? null : Colors.transparent,
);
}
}
36 changes: 36 additions & 0 deletions catalyst_voices/lib/widgets/separators/voices_divider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';

const _kDefaultIntent = 24.0;

/// The [VoicesDivider] widget is a simple wrapper around Material's [Divider]
/// widget that provides additional customization options for indentation.
///
/// It's primarily used to create horizontal dividers within the context
/// of a list or other layout with specific indentation requirements.
class VoicesDivider extends StatelessWidget {
/// A double value representing the indentation of the divider from the
/// start of the parent container.
///
/// Defaults to [_kDefaultIntent] (24.0).
final double indent;

/// A double value representing the indentation of the divider from the
/// end of the parent container.
///
/// Defaults to [_kDefaultIntent] (24.0).
final double endIntent;

const VoicesDivider({
super.key,
this.indent = _kDefaultIntent,
this.endIntent = _kDefaultIntent,
});

@override
Widget build(BuildContext context) {
return Divider(
indent: indent,
endIndent: endIntent,
);
}
}
60 changes: 60 additions & 0 deletions catalyst_voices/lib/widgets/separators/voices_text_divider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
import 'package:flutter/material.dart';

/// A divider with text placed in the middle.
///
/// This widget is a variation of [Divider] that displays a text child centered
/// between two divider lines. It provides customization options for indent,
/// gap, and overall size.
///
/// Example usage:
///
/// ```dart
/// VoicesTextDivider(
/// child: Text('My Name'),
/// ),
/// ```
class VoicesTextDivider extends StatelessWidget {
/// The indentation of the divider lines from the start and end of the row.
final double indent;

/// The gap between the divider lines and the text child.
final double nameGap;

/// The size of the row containing the divider lines and text child.
final MainAxisSize mainAxisSize;

/// The text to display in the middle of the divider.
final Widget child;

const VoicesTextDivider({
super.key,
this.indent = 24,
this.nameGap = 8,
this.mainAxisSize = MainAxisSize.min,
required this.child,
});

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);

return DividerTheme(
data: const DividerThemeData(space: 40),
child: Row(
mainAxisSize: mainAxisSize,
children: [
Expanded(child: Divider(indent: indent)),
SizedBox(width: nameGap),
DefaultTextStyle(
style: (theme.textTheme.bodyLarge ?? const TextStyle())
.copyWith(color: theme.colors.textOnPrimary),
child: child,
),
SizedBox(width: nameGap),
Expanded(child: Divider(endIndent: indent)),
],
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';

/// A vertical divider that ensures consistent color based on [DividerTheme].
///
/// This widget wraps [VerticalDivider] and explicitly sets its `color`
/// property to match the current [DividerTheme]. This is necessary because M3
/// overrides the default color behavior.
///
/// You can customize the divider's appearance using the [indent] and
/// [endIndent] properties.
class VoicesVerticalDivider extends StatelessWidget {
/// The indentation of the divider from the start of the column.
///
/// See [VerticalDivider.indent] for more details.
final double? indent;

/// The indentation of the divider from the end of the column.
///
/// See [VerticalDivider.endIndent] for more details.
final double? endIndent;

const VoicesVerticalDivider({
super.key,
this.indent,
this.endIndent,
});

@override
Widget build(BuildContext context) {
return VerticalDivider(
indent: indent,
endIndent: endIndent,
// M3 will override it and use outline color that's why setting
// it explicitly.
color: DividerTheme.of(context).color,
);
}
}
47 changes: 47 additions & 0 deletions catalyst_voices/lib/widgets/toggles/voices_switch.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
import 'package:flutter/material.dart';

/// A Voices version of a [Switch] widget with optional thumb icon
/// customization.
///
/// This widget provides a basic switch functionality with the ability to
/// display a custom icon on the thumb.
class VoicesSwitch extends StatelessWidget {
/// The current state of the switch.
final bool value;

/// An optional icon to display on the switch thumb.
final IconData? thumbIcon;

/// A callback function triggered when the switch value changes.
final ValueChanged<bool>? onChanged;

/// Creates a [VoicesSwitch] widget.
///
/// The [value] argument is required and specifies the initial state
/// of the switch.
const VoicesSwitch({
super.key,
required this.value,
this.thumbIcon,
this.onChanged,
});

@override
Widget build(BuildContext context) {
final thumbIcon = this.thumbIcon;

return Switch(
value: value,
onChanged: onChanged,
thumbIcon: thumbIcon != null
? WidgetStatePropertyAll(
Icon(
thumbIcon,
color: Theme.of(context).colors.iconsForeground,
),
)
: null,
);
}
}
6 changes: 6 additions & 0 deletions catalyst_voices/lib/widgets/widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ export 'buttons/voices_icon_button.dart';
export 'buttons/voices_outlined_button.dart';
export 'buttons/voices_segmented_button.dart';
export 'buttons/voices_text_button.dart';
export 'indicators/voices_circular_progress_indicator.dart';
export 'indicators/voices_linear_progress_indicator.dart';
export 'separators/voices_divider.dart';
export 'separators/voices_text_divider.dart';
export 'separators/voices_vertical_divider.dart';
export 'text_field/voices_email_text_field.dart';
export 'text_field/voices_password_text_field.dart';
export 'text_field/voices_text_field.dart';
export 'toggles/voices_checkbox.dart';
export 'toggles/voices_checkbox_group.dart';
export 'toggles/voices_radio.dart';
export 'toggles/voices_switch.dart';
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,14 @@ ThemeData _buildThemeData(
),
dividerTheme: DividerThemeData(
color: colorScheme.outlineVariant,
space: 16,
thickness: 1,
),
progressIndicatorTheme: ProgressIndicatorThemeData(
color: colorScheme.primary,
linearTrackColor: colorScheme.secondaryContainer,
circularTrackColor: colorScheme.secondaryContainer,
refreshBackgroundColor: colorScheme.secondaryContainer,
),
textTheme: textTheme,
colorScheme: colorScheme,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,63 @@ extension TogglesTheme on ThemeData {
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
),
// Out of the box configuration is correct
switchTheme: SwitchThemeData(
trackColor: WidgetStateProperty.resolveWith(
(states) {
if (states.contains(WidgetState.selected) &&
!states.contains(WidgetState.disabled)) {
return colorScheme.primary;
}

return colors.outlineBorderVariant?.withOpacity(0.38);
},
),
trackOutlineColor: WidgetStateProperty.resolveWith(
(states) {
if (states.contains(WidgetState.selected) &&
!states.contains(WidgetState.disabled)) {
return colorScheme.primary;
}

return colors.outlineBorder;
},
),
trackOutlineWidth: WidgetStateProperty.resolveWith(
(states) {
if (states.contains(WidgetState.selected)) {
return states.contains(WidgetState.disabled) ? 1.0 : null;
}

return 2.0;
},
),
thumbColor: WidgetStateProperty.resolveWith(
(states) {
// Selected states
if (states.contains(WidgetState.selected)) {
if (states.contains(WidgetState.disabled)) {
return colorScheme.surface;
}
if (states.contains(WidgetState.pressed) ||
states.contains(WidgetState.hovered)) {
return colorScheme.primaryContainer;
}

return colorScheme.onPrimary;
}

// Not disabled and pressed or hovered
if (!states.contains(WidgetState.disabled) &&
(states.contains(WidgetState.pressed) ||
states.contains(WidgetState.hovered))) {
return colors.iconsForeground;
}

return colors.outlineBorder;
},
),
),
);
}
}
Loading

0 comments on commit e014bfc

Please sign in to comment.