build method
Resolves field geometry and feedback from CarpenterTheme, then composes the label, control slots, supporting text, and externally driven focus ring.
Implementation
@override
Widget build(BuildContext context) {
final theme = CarpenterTheme.of(context);
final effectiveFeedback = _effectiveFeedback;
final style = theme.fields.resolve(
availability: availability,
states: states,
hasError: effectiveFeedback?.isError ?? false,
);
final feedbackStyle =
effectiveFeedback != null && availability != FieldAvailability.disabled
? theme.feedback.resolve(effectiveFeedback.role)
: null;
final feedbackForeground = availability == FieldAvailability.disabled
? null
: effectiveFeedback?.isError ?? false
? style.error
: feedbackStyle?.foreground;
final startRadius = Radius.circular(
context.units(theme.shapes.radiusForField(shape.start, size)),
);
final endRadius = Radius.circular(
context.units(theme.shapes.radiusForField(shape.end, size)),
);
final borderRadius = BorderRadiusDirectional.only(
topStart: startRadius,
bottomStart: startRadius,
topEnd: endRadius,
bottomEnd: endRadius,
).resolve(Directionality.of(context));
final labelStyle = theme.typography
.fieldLabel(context, size, TypographyEmphasis.medium)
.copyWith(color: style.label);
final supportingStyle = theme.typography
.fieldSupporting(context, size, TypographyEmphasis.regular)
.copyWith(color: feedbackForeground ?? style.supporting);
final fieldHeight = theme.sizes.fieldExtent(context, size);
final horizontal = context.units(theme.spacing.fieldHorizontal(size));
final vertical = fixedHeight
? 0.0
: context.units(theme.spacing.fieldVertical(size));
final contentGap = context.units(theme.spacing.fieldContentGapFor(size));
final supportingText = effectiveFeedback?.message ?? description;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (label != null) ...[
Text.rich(
TextSpan(
style: labelStyle,
children: [
TextSpan(text: label),
if (required)
TextSpan(
text: ' *',
style: labelStyle.copyWith(color: style.error),
),
],
),
),
SizedBox(height: context.units(theme.spacing.fieldLabelGapFor(size))),
],
_FieldControlTarget(
fixedHeight: fixedHeight,
minimumTarget: context.units(theme.sizes.minimumTarget),
child: FocusRing(
visible: states.contains(WidgetState.focused),
borderRadius: borderRadius,
child: AnimatedContainer(
duration: theme.motion.transitionDuration(context),
curve: theme.motion.stateCurve,
height: fixedHeight ? fieldHeight : null,
constraints: fixedHeight
? null
: BoxConstraints(minHeight: fieldHeight),
padding: EdgeInsets.symmetric(
horizontal: horizontal,
vertical: vertical,
),
decoration: BoxDecoration(
color: style.background,
borderRadius: borderRadius,
border: Border.all(
color: feedbackForeground ?? style.border,
width: context.units(theme.shapes.fieldBorderWidth),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (leading != null) ...[
leading!,
SizedBox(width: contentGap),
],
Expanded(child: child),
if (trailing != null) ...[
SizedBox(width: contentGap),
trailing!,
],
],
),
),
),
),
if (supportingText != null) ...[
SizedBox(
height: context.units(theme.spacing.fieldSupportingGapFor(size)),
),
Text(supportingText, style: supportingStyle),
],
],
);
}