Skip to content

Commit

Permalink
feat: [MDS-917] Refactor TabBar and SegmentedControl components (#325)
Browse files Browse the repository at this point in the history
Co-authored-by: BirgittMajas <79840500+BirgittMajas@users.noreply.github.com>
  • Loading branch information
GittHub-d and GittHub-d committed Dec 21, 2023
1 parent 3791d46 commit 13256fc
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 35 deletions.
99 changes: 98 additions & 1 deletion example/lib/src/storybook/stories/tab_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,25 @@ import 'package:flutter/material.dart';
import 'package:moon_design/moon_design.dart';
import 'package:storybook_flutter/storybook_flutter.dart';

class TabBarStory extends StatelessWidget {
class TabBarStory extends StatefulWidget {
static const path = '/tab_bar';

const TabBarStory({super.key});

@override
State<TabBarStory> createState() => _TabBarStoryState();
}

class _TabBarStoryState extends State<TabBarStory> with SingleTickerProviderStateMixin {
late TabController tabController;

@override
void initState() {
super.initState();

tabController = TabController(length: 3, vsync: this);
}

@override
Widget build(BuildContext context) {
final tabsSizeKnob = context.knobs.nullable.options(
Expand Down Expand Up @@ -212,6 +226,89 @@ class TabBarStory extends StatelessWidget {
),
],
),
const TextDivider(text: "Pre-made MoonTabBar with TabBarView"),
MoonTabBar(
isExpanded: true,
tabController: tabController,
tabs: [
MoonTab(
label: const Text('Tab1'),
tabStyle: tabStyle,
),
MoonTab(
label: const Text('Tab2'),
tabStyle: tabStyle,
),
MoonTab(
label: const Text('Tab3'),
tabStyle: tabStyle,
),
],
),
SizedBox(
height: 112,
child: TabBarView(
controller: tabController,
children: [
Container(
color: context.moonColors!.whis60,
padding: const EdgeInsets.all(16),
child: Stack(
children: [
const Align(
child: Text('Tab1'),
),
Align(
alignment: Alignment.centerRight,
child: GestureDetector(
onTap: () => tabController.animateTo(1),
child: const Icon(MoonIcons.controls_chevron_right_24_light),
),
),
],
),
),
Container(
color: context.moonColors!.frieza60,
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () => tabController.animateTo(0),
child: const Icon(MoonIcons.controls_chevron_left_24_light),
),
const Text('Tab2'),
GestureDetector(
onTap: () => tabController.animateTo(2),
child: const Icon(MoonIcons.controls_chevron_right_24_light),
),
],
),
),
Container(
color: context.moonColors!.whis60,
padding: const EdgeInsets.all(16),
child: Stack(
children: [
const Align(
child: Text('Tab3'),
),
Align(
alignment: Alignment.centerLeft,
child: GestureDetector(
onTap: () => tabController.animateTo(1),
child: const Icon(
MoonIcons.controls_chevron_left_24_light,
),
),
),
],
),
),
],
),
),
],
),
],
Expand Down
8 changes: 4 additions & 4 deletions lib/src/widgets/common/base_segmented_tab_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
class BaseSegmentedTabBar extends StatefulWidget {
final bool isExpanded;
final double gap;
final int selectedIndex;
final int initialIndex;
final TabController? tabController;
final ValueChanged<int> valueChanged;
final List<Widget> children;
Expand All @@ -12,7 +12,7 @@ class BaseSegmentedTabBar extends StatefulWidget {
super.key,
required this.isExpanded,
required this.gap,
required this.selectedIndex,
required this.initialIndex,
this.tabController,
required this.valueChanged,
required this.children,
Expand All @@ -39,7 +39,7 @@ class _BaseSegmentedTabBarState extends State<BaseSegmentedTabBar> with TickerPr

_controller = widget.tabController ??
DefaultTabController.maybeOf(context) ??
TabController(length: widget.children.length, vsync: this, initialIndex: widget.selectedIndex);
TabController(length: widget.children.length, vsync: this, initialIndex: widget.initialIndex);
}

@override
Expand All @@ -63,7 +63,7 @@ class _BaseSegmentedTabBarState extends State<BaseSegmentedTabBar> with TickerPr

void _handleTap(int index) {
assert(index >= 0 && index < widget.children.length);
_controller!.animateTo(index);
_controller!.index = index;
widget.valueChanged.call(index);
}

Expand Down
44 changes: 33 additions & 11 deletions lib/src/widgets/segmented_control/segmented_control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,19 @@ class MoonSegmentedControl extends StatefulWidget {
/// The padding of the MoonSegmentedControl.
final EdgeInsetsGeometry? padding;

/// The index of initially selected segment.
final int selectedIndex;
/// The index of initially selected segment. If [tabController] is provided,
/// then [initialIndex] is ignored and [tabController] initial index is used instead.
/// To update the segment index externally, use [tabController].
final int initialIndex;

/// The size of the MoonSegmentedControl.
final MoonSegmentedControlSize? segmentedControlSize;

/// Custom decoration for the MoonSegmentedControl.
final Decoration? decoration;

/// Controller of MoonSegmentedControl selection and animation state.
/// External controller for MoonSegmentedControl segment selection and animation state.
/// If [tabController] is provided, then [initialIndex] is ignored and [tabController] initial index is used instead.
final TabController? tabController;

/// Callback that returns current selected segment index.
Expand All @@ -90,7 +93,7 @@ class MoonSegmentedControl extends StatefulWidget {
this.transitionDuration,
this.transitionCurve,
this.padding,
this.selectedIndex = 0,
this.initialIndex = 0,
this.segmentedControlSize,
this.decoration,
this.tabController,
Expand All @@ -113,7 +116,7 @@ class MoonSegmentedControl extends StatefulWidget {
this.transitionDuration,
this.transitionCurve,
this.padding,
this.selectedIndex = 0,
this.initialIndex = 0,
this.segmentedControlSize,
this.decoration,
this.tabController,
Expand All @@ -130,7 +133,7 @@ class MoonSegmentedControl extends StatefulWidget {
class _MoonSegmentedControlState extends State<MoonSegmentedControl> {
late final bool _hasDefaultSegments = widget.segments != null;

late int _selectedIndex = widget.selectedIndex;
late int _selectedIndex = widget.tabController?.index ?? widget.initialIndex;

MoonSegmentedControlSizeProperties _getMoonSegmentedControlSize(
BuildContext context,
Expand Down Expand Up @@ -161,11 +164,26 @@ class _MoonSegmentedControlState extends State<MoonSegmentedControl> {
}
}

void _handleSegmentChange() {
final int animationValue = widget.tabController?.animation?.value.round() ?? 0;

if (animationValue != _selectedIndex) {
setState(() {
_selectedIndex = animationValue;

widget.onSegmentChanged?.call(animationValue);
_updateSegmentsSelectedStatus();
});
}
}

@override
void initState() {
super.initState();

_updateSegmentsSelectedStatus();

widget.tabController?.animation?.addListener(_handleSegmentChange);
}

@override
Expand Down Expand Up @@ -216,9 +234,9 @@ class _MoonSegmentedControlState extends State<MoonSegmentedControl> {
),
child: BaseSegmentedTabBar(
gap: effectiveGap,
selectedIndex: widget.selectedIndex,
tabController: widget.tabController,
isExpanded: widget.isExpanded,
initialIndex: widget.initialIndex,
tabController: widget.tabController,
children: _hasDefaultSegments
? List.generate(
widget.segments!.length,
Expand All @@ -244,10 +262,14 @@ class _MoonSegmentedControlState extends State<MoonSegmentedControl> {
if (_selectedIndex == newIndex) return;
if (widget.isDisabled) return;

widget.onSegmentChanged?.call(newIndex);
_updateSegmentsSelectedStatus();
setState(() {
_selectedIndex = newIndex;

setState(() => _selectedIndex = newIndex);
if (widget.tabController == null) {
widget.onSegmentChanged?.call(newIndex);
_updateSegmentsSelectedStatus();
}
});
},
),
),
Expand Down
60 changes: 41 additions & 19 deletions lib/src/widgets/tab_bar/tab_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,19 @@ class MoonTabBar extends StatefulWidget {
/// The padding of the MoonTabBar.
final EdgeInsetsGeometry? padding;

/// The index of initially selected tab.
final int selectedIndex;
/// The index of initially selected tab. If [tabController] is provided,
/// then [initialIndex] is ignored and [tabController] initial index is used instead.
/// To update the tab index externally, use [tabController].
final int initialIndex;

/// The size of the MoonTabBar.
final MoonTabBarSize? tabBarSize;

/// Custom decoration of the MoonTabBar.
final Decoration? decoration;

/// Controller of MoonTabBar selection and animation state.
/// External controller for MoonTabBar tab selection and animation state. If [tabController] is provided,
/// then [initialIndex] is ignored and [tabController] initial index is used instead.
final TabController? tabController;

/// Callback that returns current selected tab index.
Expand All @@ -86,7 +89,7 @@ class MoonTabBar extends StatefulWidget {
this.transitionDuration,
this.transitionCurve,
this.padding,
this.selectedIndex = 0,
this.initialIndex = 0,
this.tabBarSize,
this.decoration,
this.tabController,
Expand All @@ -107,7 +110,7 @@ class MoonTabBar extends StatefulWidget {
this.transitionDuration,
this.transitionCurve,
this.padding,
this.selectedIndex = 0,
this.initialIndex = 0,
this.tabBarSize,
this.decoration,
this.tabController,
Expand All @@ -128,7 +131,7 @@ class MoonTabBar extends StatefulWidget {
this.transitionDuration,
this.transitionCurve,
this.padding,
this.selectedIndex = 0,
this.initialIndex = 0,
this.tabBarSize,
this.decoration,
this.tabController,
Expand All @@ -144,7 +147,7 @@ class MoonTabBar extends StatefulWidget {
}

class _MoonTabBarState extends State<MoonTabBar> {
late int _selectedIndex = widget.selectedIndex;
late int _selectedIndex = widget.tabController?.index ?? widget.initialIndex;
late MoonTabBarVariant _tabBarVariant;

late Duration _effectiveTransitionDuration;
Expand Down Expand Up @@ -188,14 +191,16 @@ class _MoonTabBarState extends State<MoonTabBar> {
}
}

List<Widget> _generateTabs() {
switch (_tabBarVariant) {
case MoonTabBarVariant.indicator:
return _generateIndicatorTabs();
case MoonTabBarVariant.pill:
return _generatePillTabs();
default:
return _generateCustomTabs();
void _handleTabChange() {
final int animationValue = widget.tabController?.animation?.value.round() ?? 0;

if (animationValue != _selectedIndex) {
setState(() {
_selectedIndex = animationValue;

widget.onTabChanged?.call(animationValue);
_updateTabsSelectedStatus();
});
}
}

Expand All @@ -205,6 +210,19 @@ class _MoonTabBarState extends State<MoonTabBar> {

_setSelectedTabBarVariant();
_updateTabsSelectedStatus();

widget.tabController?.animation?.addListener(_handleTabChange);
}

List<Widget> _generateTabs() {
switch (_tabBarVariant) {
case MoonTabBarVariant.indicator:
return _generateIndicatorTabs();
case MoonTabBarVariant.pill:
return _generatePillTabs();
default:
return _generateCustomTabs();
}
}

List<Widget> _generateIndicatorTabs() {
Expand Down Expand Up @@ -271,18 +289,22 @@ class _MoonTabBarState extends State<MoonTabBar> {
child: BaseSegmentedTabBar(
gap: effectiveGap,
isExpanded: widget.isExpanded,
selectedIndex: widget.selectedIndex,
initialIndex: widget.initialIndex,
tabController: widget.tabController,
children: _generateTabs(),
valueChanged: (int newIndex) {
if (_selectedIndex == newIndex) return;
if (widget.tabs != null && widget.tabs![newIndex].disabled) return;
if (widget.pillTabs != null && widget.pillTabs![newIndex].disabled) return;

widget.onTabChanged?.call(newIndex);
_updateTabsSelectedStatus();
setState(() {
_selectedIndex = newIndex;

setState(() => _selectedIndex = newIndex);
if (widget.tabController == null) {
widget.onTabChanged?.call(newIndex);
_updateTabsSelectedStatus();
}
});
},
),
);
Expand Down

0 comments on commit 13256fc

Please sign in to comment.