Skip to content

Commit

Permalink
Improved number input layout
Browse files Browse the repository at this point in the history
  • Loading branch information
sunarya-thito committed Nov 1, 2024
1 parent a30466c commit 8d747e7
Showing 1 changed file with 87 additions and 128 deletions.
215 changes: 87 additions & 128 deletions lib/src/components/form/number_input.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';

Expand Down Expand Up @@ -66,12 +65,16 @@ class _NumberInputState extends State<NumberInput> {
Widget build(BuildContext context) {
if (widget.showButtons) {
final theme = Theme.of(context);
return OutlinedContainer(
borderRadius: theme.borderRadiusMd,
child: _NumberInputFlex(
return IntrinsicHeight(
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
buildTextField(context),
buildButton(context),
Flexible(child: buildTextField(context)),
SizedBox(
height: 32 * theme.scaling,
child: buildButton(context, theme),
),
],
),
);
Expand All @@ -90,7 +93,7 @@ class _NumberInputState extends State<NumberInput> {
);
}

Widget buildButton(BuildContext context) {
Widget buildButton(BuildContext context, ThemeData theme) {
return GestureDetector(
onPanUpdate: (details) {
if (details.delta.dy > 0) {
Expand Down Expand Up @@ -137,21 +140,23 @@ class _NumberInputState extends State<NumberInput> {
}
}
},
child: IntrinsicWidth(
child: Stack(
fit: StackFit.passthrough,
children: [
Column(
child: Stack(
fit: StackFit.passthrough,
children: [
IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
Flexible(
child: Button(
style: _buttonStyle.copyWith(
decoration: (context, states, value) {
if (value is BoxDecoration) {
return value.copyWith(
borderRadius: BorderRadius.zero,
borderRadius: BorderRadiusDirectional.only(
topEnd: Radius.circular(theme.radiusSm),
),
);
}
return value;
Expand Down Expand Up @@ -179,13 +184,15 @@ class _NumberInputState extends State<NumberInput> {
),
),
),
Expanded(
Flexible(
child: Button(
style: _buttonStyle.copyWith(
decoration: (context, states, value) {
if (value is BoxDecoration) {
return value.copyWith(
borderRadius: BorderRadius.zero);
borderRadius: BorderRadiusDirectional.only(
bottomEnd: Radius.circular(theme.radiusSm),
));
}
return value;
},
Expand Down Expand Up @@ -214,18 +221,18 @@ class _NumberInputState extends State<NumberInput> {
),
],
),
const Positioned.fill(
child: Center(
child: MouseRegion(
cursor: SystemMouseCursors.resizeUpDown,
hitTestBehavior: HitTestBehavior.translucent,
child: SizedBox(
width: double.infinity,
height: 8,
))),
)
],
),
),
const Positioned.fill(
child: Center(
child: MouseRegion(
cursor: SystemMouseCursors.resizeUpDown,
hitTestBehavior: HitTestBehavior.translucent,
child: SizedBox(
width: double.infinity,
height: 8,
))),
)
],
),
),
);
Expand Down Expand Up @@ -256,106 +263,58 @@ class _NumberInputState extends State<NumberInput> {
Widget buildTextField(BuildContext context) {
final theme = Theme.of(context);
final scaling = theme.scaling;
return TextField(
leading: widget.leading,
trailing: widget.trailing == null
? null
: Padding(
padding: EdgeInsets.only(
right: widget.showButtons == false ? 0 : 24 * scaling),
child: widget.trailing),
padding: widget.padding ??
EdgeInsets.symmetric(
horizontal: 10 * scaling,
vertical: 10 * scaling,
),
style: widget.style,
inputFormatters: [
if (!widget.allowDecimals) FilteringTextInputFormatter.digitsOnly,
if (widget.allowDecimals)
FilteringTextInputFormatter.allow(
RegExp(r'^-?[0-9]+\.?[0-9]*$'),
),
],
controller: _controller,
onEditingComplete: () {
double value = double.tryParse(_controller.text) ?? _lastValidValue;
if (widget.min != null && value < widget.min!) {
value = widget.min!;
} else if (widget.max != null && value > widget.max!) {
value = widget.max!;
}
_lastValidValue = value;
_controller.text = _valueAsString;
widget.onChanged?.call(_lastValidValue);
},
border: !widget.showButtons,
enabled: widget.enabled ?? true,
initialValue: _valueAsString,
keyboardType:
TextInputType.numberWithOptions(decimal: widget.allowDecimals),
textAlignVertical: TextAlignVertical.center,
);
}
}

class _NumberInputFlex extends MultiChildRenderObjectWidget {
const _NumberInputFlex({
required super.children,
});

@override
RenderObject createRenderObject(BuildContext context) {
return _RenderNumberInputFlex();
}

@override
void updateRenderObject(
BuildContext context, _RenderNumberInputFlex renderObject) {}
}

class _NumberInputFlexParentData extends ContainerBoxParentData<RenderBox> {}

// A row basically, first child is TextField, second child is a column of buttons
// the second child follows the height of the first child
class _RenderNumberInputFlex extends RenderBox
with
ContainerRenderObjectMixin<RenderBox, _NumberInputFlexParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, _NumberInputFlexParentData> {
@override
void setupParentData(RenderBox child) {
if (child.parentData is! _NumberInputFlexParentData) {
child.parentData = _NumberInputFlexParentData();
}
}

@override
void paint(PaintingContext context, Offset offset) {
defaultPaint(context, offset);
}

@override
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
return defaultHitTestChildren(result, position: position);
}

@override
void performLayout() {
RenderBox textField = firstChild!;
RenderBox buttons = lastChild!;
textField.layout(constraints, parentUsesSize: true);
buttons.layout(
BoxConstraints(
minWidth: 0,
maxWidth: constraints.maxWidth,
minHeight: 0,
maxHeight: textField.size.height,
),
parentUsesSize: true);
size = Size(constraints.maxWidth, textField.size.height);
(buttons.parentData as _NumberInputFlexParentData).offset = Offset(
constraints.maxWidth - buttons.size.width,
0,
return ConstrainedBox(
constraints: BoxConstraints(
minWidth: 50 * scaling,
),
child: TextField(
expands: true,
minLines: null,
maxLines: null,
leading: widget.leading,
trailing: widget.trailing == null
? null
: Padding(
padding: EdgeInsets.only(
right: widget.showButtons == false ? 0 : 24 * scaling),
child: widget.trailing),
padding: widget.padding ??
EdgeInsets.symmetric(
horizontal: 10 * scaling,
vertical: 10 * scaling,
),
style: widget.style,
inputFormatters: [
if (!widget.allowDecimals) FilteringTextInputFormatter.digitsOnly,
if (widget.allowDecimals)
FilteringTextInputFormatter.allow(
RegExp(r'^-?[0-9]+\.?[0-9]*$'),
),
],
controller: _controller,
onEditingComplete: () {
double value = double.tryParse(_controller.text) ?? _lastValidValue;
if (widget.min != null && value < widget.min!) {
value = widget.min!;
} else if (widget.max != null && value > widget.max!) {
value = widget.max!;
}
_lastValidValue = value;
_controller.text = _valueAsString;
widget.onChanged?.call(_lastValidValue);
},
borderRadius: widget.showButtons
? BorderRadiusDirectional.only(
topStart: Radius.circular(4 * scaling),
bottomStart: Radius.circular(4 * scaling),
)
: BorderRadius.circular(4 * scaling),
enabled: widget.enabled ?? true,
initialValue: _valueAsString,
keyboardType:
TextInputType.numberWithOptions(decimal: widget.allowDecimals),
textAlignVertical: TextAlignVertical.center,
),
);
}
}

0 comments on commit 8d747e7

Please sign in to comment.