build method

  1. @override
Widget build(
  1. BuildContext context
)
override

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),
      ],
    ],
  );
}