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,169 @@
|
||||
import "dart:ui" show Color, ImageFilter;
|
||||
|
||||
import "package:flutter/painting.dart" show HSLColor;
|
||||
import "package:flutter/widgets.dart";
|
||||
|
||||
import "package:garage_ui/theme/garage_theme.dart";
|
||||
|
||||
// small pile of helpers the GarageUI widgets used to pull off the shadcn barrel.
|
||||
// theyre generic, nothing shadcn specific, so we just keep our own copies.
|
||||
|
||||
// shadcns Color helpers that the widgets lean on.
|
||||
extension ColorExtension on Color {
|
||||
// scale the alpha channel by [factor] (0..1). shadcns scaleAlpha.
|
||||
Color scaleAlpha(double factor) {
|
||||
return withValues(alpha: (a * factor).clamp(0.0, 1.0));
|
||||
}
|
||||
|
||||
// a readable foreground for this colour - flips lightness. shadcns getContrastColor.
|
||||
Color getContrastColor([double luminanceContrast = 1]) {
|
||||
final hsl = HSLColor.fromColor(this);
|
||||
final l = hsl.lightness;
|
||||
final target = l >= 0.5
|
||||
? l - (l * luminanceContrast)
|
||||
: l + ((1 - l) * luminanceContrast);
|
||||
return hsl.withLightness(target.clamp(0.0, 1.0)).toColor();
|
||||
}
|
||||
}
|
||||
|
||||
// default anim duration shadcn used all over (hover/colour transitions etc).
|
||||
const Duration kDefaultDuration = Duration(milliseconds: 150);
|
||||
|
||||
// widget value wins, then theme value, then the hard default. shadcns styleValue.
|
||||
T styleValue<T>({T? widgetValue, T? themeValue, required T defaultValue}) {
|
||||
return widgetValue ?? themeValue ?? defaultValue;
|
||||
}
|
||||
|
||||
// we dropped shadcns density-insets system, so this is now just a passthrough -
|
||||
// the GarageUI widgets only ever hand it plain EdgeInsets anyway.
|
||||
EdgeInsetsGeometry resolveEdgeInsets(
|
||||
EdgeInsetsGeometry padding,
|
||||
double basePadding,
|
||||
) {
|
||||
return padding;
|
||||
}
|
||||
|
||||
// shrinks a border radius by the border width so an inner surface tucks neatly
|
||||
// inside its border. shadcns subtractByBorder.
|
||||
BorderRadius subtractByBorder(BorderRadius radius, double borderWidth) {
|
||||
Radius sub(Radius r) => Radius.elliptical(
|
||||
(r.x - borderWidth).clamp(0.0, double.infinity),
|
||||
(r.y - borderWidth).clamp(0.0, double.infinity),
|
||||
);
|
||||
return BorderRadius.only(
|
||||
topLeft: sub(radius.topLeft),
|
||||
topRight: sub(radius.topRight),
|
||||
bottomLeft: sub(radius.bottomLeft),
|
||||
bottomRight: sub(radius.bottomRight),
|
||||
);
|
||||
}
|
||||
|
||||
// used to be a density-aware Padding in shadcn. our padding is already resolved
|
||||
// by the time it gets here, so this is just a Padding.
|
||||
class DensityContainerPadding extends StatelessWidget {
|
||||
const DensityContainerPadding({
|
||||
super.key,
|
||||
required this.padding,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final EdgeInsetsGeometry padding;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Padding(padding: padding, child: child);
|
||||
}
|
||||
|
||||
// shadcn used this to tell whether a surface was being rendered inside a sheet
|
||||
// so it could drop its own rounding/border. the app doesnt use sheets, so its
|
||||
// always false.
|
||||
class SheetOverlayHandler {
|
||||
const SheetOverlayHandler._();
|
||||
static bool isSheetOverlay(BuildContext context) => false;
|
||||
}
|
||||
|
||||
// draws a focus ring around a child when [focused]. shadcns FocusOutline, cut
|
||||
// down to what the text field needs.
|
||||
class FocusOutline extends StatelessWidget {
|
||||
const FocusOutline({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.focused,
|
||||
this.borderRadius,
|
||||
this.color,
|
||||
this.width = 1.0,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final bool focused;
|
||||
final BorderRadiusGeometry? borderRadius;
|
||||
final Color? color;
|
||||
final double width;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!focused) return child;
|
||||
final ring = color ?? GarageTheme.of(context).colorScheme.ring;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: borderRadius,
|
||||
// draw the ring OUTSIDE the box so it doesnt inset the child and
|
||||
// change the field size when focus comes and goes.
|
||||
border: Border.all(
|
||||
color: ring,
|
||||
width: width,
|
||||
strokeAlign: BorderSide.strokeAlignOutside,
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// blurs whatever is behind the child (glassmorphism on popovers/cards). ported
|
||||
// straight from shadcns outlined_container.dart.
|
||||
class SurfaceBlur extends StatefulWidget {
|
||||
const SurfaceBlur({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.surfaceBlur,
|
||||
this.borderRadius,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final double? surfaceBlur;
|
||||
final BorderRadiusGeometry? borderRadius;
|
||||
|
||||
@override
|
||||
State<SurfaceBlur> createState() => _SurfaceBlurState();
|
||||
}
|
||||
|
||||
class _SurfaceBlurState extends State<SurfaceBlur> {
|
||||
final GlobalKey _mainContainerKey = GlobalKey();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.surfaceBlur == null || widget.surfaceBlur! <= 0) {
|
||||
return KeyedSubtree(key: _mainContainerKey, child: widget.child);
|
||||
}
|
||||
return Stack(
|
||||
fit: StackFit.passthrough,
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: ClipRRect(
|
||||
borderRadius: widget.borderRadius ?? BorderRadius.zero,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: widget.surfaceBlur!,
|
||||
sigmaY: widget.surfaceBlur!,
|
||||
),
|
||||
// needs a child or it wont actually blur anything
|
||||
child: const SizedBox(),
|
||||
),
|
||||
),
|
||||
),
|
||||
KeyedSubtree(key: _mainContainerKey, child: widget.child),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user