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? 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 createState() => _SurfaceBlurState(); } class _SurfaceBlurState extends State { 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), ], ); } }