Skip to content

Commit

Permalink
select
Browse files Browse the repository at this point in the history
  • Loading branch information
tilucasoli committed Dec 18, 2024
1 parent 29ca8f1 commit d753973
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 140 deletions.
10 changes: 6 additions & 4 deletions packages/remix/demo/lib/components/select.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,17 @@ class _SelectDemoState extends State<SelectDemo> {
SizedBox(
width: 200,
child: Select<String>(
disabled:
context.knobs.boolean(label: 'disabled', initialValue: false),
variants: [context.knobs.variant(FortalezaSelectStyle.variants)],
value: selectedValue,
onChanged: (value) => setState(() => selectedValue = value),
button: (spec) => spec(
trigger: (spec) => spec(
text: selectedValue,
trailingIcon: m.Icons.keyboard_arrow_down_rounded,
),
disabled: context.knobs.boolean(
label: 'disabled',
initialValue: false,
),
variants: [context.knobs.variant(FortalezaSelectStyle.variants)],
items: List.generate(
items.length,
(index) => SelectMenuItem<String>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1036,6 +1036,7 @@
/Users/lucasoliveira/Developer/Mix/PoCs/remix_toast/packages/mix/packages/remix/lib/src/helpers/color_utils.dart
/Users/lucasoliveira/Developer/Mix/PoCs/remix_toast/packages/mix/packages/remix/lib/src/helpers/component_builder.dart
/Users/lucasoliveira/Developer/Mix/PoCs/remix_toast/packages/mix/packages/remix/lib/src/helpers/context_ext.dart
/Users/lucasoliveira/Developer/Mix/PoCs/remix_toast/packages/mix/packages/remix/lib/src/helpers/object_ext.dart
/Users/lucasoliveira/Developer/Mix/PoCs/remix_toast/packages/mix/packages/remix/lib/src/helpers/overlay.dart
/Users/lucasoliveira/Developer/Mix/PoCs/remix_toast/packages/mix/packages/remix/lib/src/helpers/spec/composited_transform_follower_spec.dart
/Users/lucasoliveira/Developer/Mix/PoCs/remix_toast/packages/mix/packages/remix/lib/src/helpers/spec/composited_transform_follower_spec.g.dart
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class DropdownMenuState extends State<DropdownMenu> {
onTapOutside: widget.onPressOutside,
showOverlay: widget.open,
animationDuration: animatedStyle?.animated.duration ?? Duration.zero,
link: _link,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,36 @@ class SelectButtonSpecWidget extends StatelessWidget {

@override
Widget build(BuildContext context) {
return SpecBuilder(
inherit: true,
builder: (context) {
final button = SelectSpec.of(context).button;

final container = button.container;
final label = button.label;
final icon = button.icon;

return container(
direction: Axis.horizontal,
children: [label(text), icon(trailingIcon)],
);
final select = context.findAncestorStateOfType<SelectState>();

if (select == null) {
throw Exception('SelectButton must be a child of a Select');
}

final style = select.widget.style ?? context.remix.components.select;
final configuration = SpecConfiguration(context, SelectSpecUtility.self);
final appliedStyle =
style.makeStyle(configuration).applyVariants(select.widget.variants);

return Pressable(
onPress: () {
select.openMenu();
},
child: SpecBuilder(
style: appliedStyle,
builder: (context) {
final button = SelectSpec.of(context).button;

final container = button.container;
final label = button.label;
final icon = button.icon;

return container(
direction: Axis.horizontal,
children: [label(text), icon(trailingIcon)],
);
},
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,15 @@ class SelectMenuItemWidget extends StatelessWidget {

@override
Widget build(BuildContext context) {
return SpecBuilder(
inherit: true,
builder: (context) {
final item = SelectSpec.of(context).item;
final item = SelectSpec.of(context).item;

final container = item.container;
final icon = item.icon;
final text = item.text;
final container = item.container;
final icon = item.icon;
final text = item.text;

return container(
direction: Axis.horizontal,
children: [if (iconData != null) icon(iconData), text(this.text)],
);
},
return container(
direction: Axis.horizontal,
children: [if (iconData != null) icon(iconData), text(this.text)],
);
}
}
2 changes: 2 additions & 0 deletions packages/remix/lib/src/components/select/select.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import 'package:mix_annotations/mix_annotations.dart';

import '../../core/theme/remix_theme.dart';
import '../../helpers/component_builder.dart';
import '../../helpers/object_ext.dart';
import '../../helpers/overlay.dart';
import '../../helpers/spec/composited_transform_follower_spec.dart';

part 'button/select_button.dart';
Expand Down
5 changes: 4 additions & 1 deletion packages/remix/lib/src/components/select/select_style.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ class SelectStyle extends SpecStyle<SelectSpecUtility> {
...buttonStyle,
...menuStyle,
...positionStyle,
]);
]).animate(
duration: const Duration(milliseconds: 1500),
curve: Curves.easeInOut,
);
}
}

Expand Down
175 changes: 68 additions & 107 deletions packages/remix/lib/src/components/select/select_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class Select<T> extends StatefulWidget {
super.key,
required this.value,
required this.onChanged,
required this.button,
required this.trigger,
required this.items,
this.variants = const [],
this.style,
Expand All @@ -34,7 +34,7 @@ class Select<T> extends StatefulWidget {
/// Builder function that creates the button portion of the select component.
/// When tapped, this button will display the dropdown menu.
/// This allows customizing how the button is displayed.
final WidgetSpecBuilder<SelectButtonSpec> button;
final WidgetSpecBuilder<SelectButtonSpec> trigger;

/// {@macro remix.component.disabled}
final bool disabled;
Expand All @@ -49,15 +49,9 @@ class Select<T> extends StatefulWidget {

class SelectState<T> extends State<Select<T>>
with SingleTickerProviderStateMixin {
final OverlayPortalController _tooltipController = OverlayPortalController();
late final MixWidgetStateController _menuStateController;
late final MixWidgetStateController _buttonStateController;

final _baseAnimation = (
duration: const Duration(milliseconds: 100),
curve: Curves.decelerate,
);

final _link = LayerLink();

@override
Expand All @@ -76,6 +70,18 @@ class SelectState<T> extends State<Select<T>>
_buttonStateController.disabled = widget.disabled;
}

void openMenu() {
setState(() {
_menuStateController.selected = true;
});
}

void closeMenu() {
setState(() {
_menuStateController.selected = false;
});
}

@override
void dispose() {
_buttonStateController.dispose();
Expand All @@ -90,108 +96,63 @@ class SelectState<T> extends State<Select<T>>
final appliedStyle =
style.makeStyle(configuration).applyVariants(widget.variants);

return SpecBuilder(
controller: _buttonStateController,
style: appliedStyle,
builder: (context) {
final button = SelectSpec.of(context).button;
final position = SelectSpec.of(context).position;

return CompositedTransformTarget(
link: _link,
child: OverlayPortal(
controller: _tooltipController,
overlayChildBuilder: (BuildContext context) {
return Stack(children: [
GestureDetector(
onTap: () => hide(),
child: Container(color: Colors.transparent),
),
CompositedTransformFollower(
link: _link,
targetAnchor:
position.targetAnchor.resolve(TextDirection.ltr),
followerAnchor:
position.followerAnchor.resolve(TextDirection.ltr),
child: SpecBuilder(
controller: _menuStateController,
style: appliedStyle.animate(
duration: _baseAnimation.duration,
curve: _baseAnimation.curve,
onEnd: () {
if (_menuStateController.selected == false) {
_tooltipController.hide();
}
final animatedStyle = appliedStyle.cast<AnimatedStyle>();

return OverlayWrapper(
target: RepaintBoundary(
child: SpecBuilder(
controller: _buttonStateController,
style: appliedStyle,
builder: (context) {
final buttonSpec = SelectSpec.of(context).button;

return widget.trigger(buttonSpec);
},
),
),
overlayChild: SpecBuilder(
controller: _menuStateController,
style: appliedStyle,
builder: (context) {
final select = SelectSpec.of(context);
final menu = select.menu;

final FlexContainer = menu.container.copyWith(
box: menu.container.box.copyWith(
width: menu.autoWidth ? _link.leaderSize!.width : null,
),
);

return AnimatedBoxSpecWidget(
spec: FlexContainer.box,
duration: animatedStyle?.animated.duration ?? Duration.zero,
curve: animatedStyle?.animated.curve ?? Curves.easeInOut,
child: FlexContainer.flex(
direction: Axis.vertical,
children: widget.items
.map(
(item) => Pressable(
onPress: () {
widget.onChanged(item.value);
closeMenu();
},
child: SpecBuilder(
style: appliedStyle,
builder: (context) {
return item.child;
},
),
),
builder: (context) {
final select = SelectSpec.of(context);
final menu = select.menu;

final Container = menu.container.copyWith(
box: menu.container.box.copyWith(
width:
menu.autoWidth ? _link.leaderSize!.width : null,
),
);

return Container(
direction: Axis.vertical,
children: widget.items.map((item) {
return Pressable(
onPress: () {
widget.onChanged(item.value);
hide();
},
child: SpecBuilder(
style: appliedStyle.animate(
duration: _baseAnimation.duration,
curve: _baseAnimation.curve,
),
builder: (context) {
return item.child;
},
),
);
}).toList(),
);
},
),
),
]);
},
child: RepaintBoundary(
child: Pressable(
enabled: !widget.disabled,
onPress: onTap,
child: widget.button(button),
),
)
.toList(),
),
),
);
},
);
},
),
onTapOutside: closeMenu,
showOverlay: _menuStateController.selected,
animationDuration: animatedStyle?.animated.duration ?? Duration.zero,
link: _link,
);
}

void show() {
if (!_tooltipController.isShowing) {
_tooltipController.show();
}

WidgetsBinding.instance.addPostFrameCallback((_) {
_menuStateController.selected = true;
});
}

void hide() {
_menuStateController.selected = false;
}

void onTap() {
if (!_tooltipController.isShowing) {
show();
} else {
hide();
}
}
}
8 changes: 5 additions & 3 deletions packages/remix/lib/src/helpers/overlay.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class OverlayWrapper extends StatefulWidget {
this.onTapOutside,
this.showOverlay = true,
this.animationDuration = const Duration(),
required this.link,
});

/// The trigger widget that opens the dropdown menu.
Expand Down Expand Up @@ -42,12 +43,13 @@ class OverlayWrapper extends StatefulWidget {

final Duration animationDuration;

final LayerLink link;

@override
State<OverlayWrapper> createState() => OverlayWrapperState();
}

class OverlayWrapperState extends State<OverlayWrapper> {
final _link = LayerLink();
OverlayPortalController? _controller;
Timer? _timer;

Expand Down Expand Up @@ -91,7 +93,7 @@ class OverlayWrapperState extends State<OverlayWrapper> {
@override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: _link,
link: widget.link,
child: OverlayPortal(
controller: _controller!,
overlayChildBuilder: (BuildContext context) {
Expand All @@ -102,7 +104,7 @@ class OverlayWrapperState extends State<OverlayWrapper> {
child: Container(color: Colors.transparent),
),
CompositedTransformFollower(
link: _link,
link: widget.link,
offset: widget.offset,
targetAnchor: widget.targetAnchor,
followerAnchor: widget.followerAnchor,
Expand Down

0 comments on commit d753973

Please sign in to comment.