The Garage SDKs, in the open
garage_auth, garage_entitlements, garage_iap and garage_ui, moved out of Garage-Services and Metro-Map-Maker into one public repo. MIT, one readme, docs under docs/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013F4NWNvYcdeSgqbWMT1VQ7
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import "package:flutter/widgets.dart";
|
||||
|
||||
import "package:garage_ui/theme/garage_theme.dart";
|
||||
|
||||
/// How long a field takes to admit theres something wrong with it, and how
|
||||
/// long the row takes to make room for saying so. One number for both: the
|
||||
/// outline and the line under it are one event and shouldnt arrive at
|
||||
/// different times.
|
||||
const Duration kFieldErrorDuration = Duration(milliseconds: 180);
|
||||
const Curve kFieldErrorCurve = Curves.easeInOutCubic;
|
||||
|
||||
/// "This one was rejected", published over a control rather than set on it.
|
||||
///
|
||||
/// A row knows the value is wrong. What the row is HOLDING could be anything -
|
||||
/// a text box, a select, a group of them - so the row cant reach in and colour
|
||||
/// it. It says so here instead, and every control that draws itself a border
|
||||
/// reads this on the way past.
|
||||
///
|
||||
/// Only the border moves. A field whose fill goes red reads as a state the
|
||||
/// thing is permanently in; a red outline reads as a correction, which is what
|
||||
/// this is - and it goes back to normal the moment the value does.
|
||||
class FieldErrorScope extends InheritedWidget {
|
||||
const FieldErrorScope({
|
||||
super.key,
|
||||
required this.invalid,
|
||||
required super.child,
|
||||
});
|
||||
|
||||
final bool invalid;
|
||||
|
||||
static bool of(BuildContext context) =>
|
||||
context.dependOnInheritedWidgetOfExactType<FieldErrorScope>()?.invalid ??
|
||||
false;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(FieldErrorScope old) => old.invalid != invalid;
|
||||
}
|
||||
|
||||
/// The destructive outline, faded in OVER whatever border is allready there.
|
||||
///
|
||||
/// Over rather than instead of, deliberately. A control's fill changes the
|
||||
/// instant you hover it - button.dart says so in as many words, and a
|
||||
/// TextField matches it - so rebuilding the decoration with an animated
|
||||
/// colour in it would have dragged the fill into the transition too and made
|
||||
/// every hover ease. This sits on top and is the only thing that moves.
|
||||
///
|
||||
/// It also means a control with no border at all - a ghost select, say - gets
|
||||
/// one here for nothing, which is what "the border goes red" needs in order
|
||||
/// to mean anything.
|
||||
class FieldErrorOutline extends StatelessWidget {
|
||||
const FieldErrorOutline({
|
||||
super.key,
|
||||
required this.invalid,
|
||||
required this.borderRadius,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final bool invalid;
|
||||
final BorderRadiusGeometry borderRadius;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = GarageTheme.of(context);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
child,
|
||||
// Positioned.fill, so it takes the child's size rather than joining in
|
||||
// deciding what that size is.
|
||||
Positioned.fill(
|
||||
child: IgnorePointer(
|
||||
child: AnimatedOpacity(
|
||||
opacity: invalid ? 1.0 : 0.0,
|
||||
duration: kFieldErrorDuration,
|
||||
curve: kFieldErrorCurve,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: borderRadius,
|
||||
border: Border.all(
|
||||
color: theme.colorScheme.destructive,
|
||||
width: theme.density.controlBorderWidth,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user