Files
repertory/web/repertory/lib/widgets/app_dropdown.dart
Scott E. Graves 61e0ce4b97
All checks were successful
BlockStorage/repertory/pipeline/head This commit looks good
[ui] UI theme should match repertory blue #61
2025-09-04 18:26:22 -05:00

146 lines
3.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:repertory/constants.dart' as constants;
import 'package:repertory/helpers.dart';
class AppDropdownFormField<T> extends StatelessWidget {
const AppDropdownFormField({
super.key,
required this.labelOf,
required this.values,
this.constrainToIntrinsic = false,
this.contentPadding,
this.dropdownColor,
this.enabled = true,
this.fillColor,
this.isExpanded = false,
this.labelText,
this.maxWidth,
this.onChanged,
this.prefixIcon,
this.textStyle,
this.validator,
this.value,
this.widthMultiplier = 1.0,
});
final List<T> values;
final String Function(T value) labelOf;
final T? value;
final ValueChanged<T?>? onChanged;
final FormFieldValidator<T>? validator;
final String? labelText;
final IconData? prefixIcon;
final bool enabled;
final bool constrainToIntrinsic;
final double widthMultiplier;
final double? maxWidth;
final bool isExpanded;
final Color? dropdownColor;
final TextStyle? textStyle;
final EdgeInsetsGeometry? contentPadding;
final Color? fillColor;
double _measureTextWidth(
BuildContext context,
String text,
TextStyle? style,
) {
final tp = TextPainter(
text: TextSpan(text: text, style: style),
maxLines: 1,
textDirection: Directionality.of(context),
)..layout();
return tp.width;
}
double? _computedMaxWidth(BuildContext context) {
if (!constrainToIntrinsic) return maxWidth;
final theme = Theme.of(context);
final scheme = theme.colorScheme;
final effectiveStyle =
textStyle ??
theme.textTheme.bodyMedium?.copyWith(
color: scheme.onSurface.withValues(alpha: 0.96),
);
final longest = values.isEmpty
? ''
: values
.map((v) => labelOf(v))
.reduce((a, b) => a.length >= b.length ? a : b);
final labelW = _measureTextWidth(context, longest, effectiveStyle);
final prefixW = prefixIcon == null ? 0.0 : 48.0;
const arrowW = 32.0;
final pad = contentPadding ?? const EdgeInsets.all(constants.paddingSmall);
final padW = (pad is EdgeInsets)
? (pad.left + pad.right)
: constants.paddingSmall * 2;
final base = labelW + prefixW + arrowW + padW;
final cap =
maxWidth ?? (MediaQuery.of(context).size.width - constants.padding * 2);
return (base * widthMultiplier).clamp(0.0, cap);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final scheme = theme.colorScheme;
final effectiveFill =
fillColor ?? scheme.primary.withValues(alpha: constants.primaryAlpha);
final effectiveTextStyle =
textStyle ??
theme.textTheme.bodyMedium?.copyWith(
color: scheme.onSurface.withValues(alpha: 0.96),
);
final items = values.map((v) {
return DropdownMenuItem<T>(
value: v,
child: Text(
labelOf(v),
style: effectiveTextStyle,
overflow: TextOverflow.ellipsis,
),
);
}).toList();
final field = DropdownButtonFormField<T>(
decoration: createCommonDecoration(
scheme,
labelText ?? "",
icon: prefixIcon,
),
dropdownColor: dropdownColor ?? effectiveFill,
iconEnabledColor: scheme.onSurface.withValues(alpha: 0.90),
initialValue: value,
isExpanded: isExpanded,
items: items,
onChanged: enabled ? onChanged : null,
style: effectiveTextStyle,
validator: validator,
);
final maxW = _computedMaxWidth(context);
final wrapped = maxW == null
? field
: ConstrainedBox(
constraints: BoxConstraints(maxWidth: maxW),
child: field,
);
return Align(
alignment: Alignment.centerLeft,
heightFactor: 1.0,
child: wrapped,
);
}
}