Move apps to mobile directory structure
Relocated all app directories (client, design_system_viewer, staff) and their contents under the new 'apps/mobile' path. This change improves project organization and prepares for future platform-specific structuring.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../ui_icons.dart';
|
||||
|
||||
/// A custom AppBar for the Krow UI design system.
|
||||
///
|
||||
/// This widget provides a consistent look and feel for top app bars across the application.
|
||||
class UiAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
/// The title text to display in the app bar.
|
||||
final String? title;
|
||||
|
||||
/// A widget to display instead of the title text.
|
||||
final Widget? titleWidget;
|
||||
|
||||
/// The widget to display before the title.
|
||||
/// Usually an [IconButton] for navigation.
|
||||
final Widget? leading;
|
||||
|
||||
/// A list of Widgets to display in a row after the [title] widget.
|
||||
final List<Widget>? actions;
|
||||
|
||||
/// The height of the app bar. Defaults to [kToolbarHeight].
|
||||
final double height;
|
||||
|
||||
/// Whether the title should be centered.
|
||||
final bool centerTitle;
|
||||
|
||||
/// Signature for the callback that is called when the leading button is pressed.
|
||||
/// If [leading] is null, this callback will be used for a default back button.
|
||||
final VoidCallback? onLeadingPressed;
|
||||
|
||||
/// Whether to show a default back button if [leading] is null.
|
||||
final bool showBackButton;
|
||||
|
||||
/// This widget appears across the bottom of the app bar.
|
||||
/// Typically a [TabBar]. Only widgets that implement [PreferredSizeWidget] can be used at the bottom of an app bar.
|
||||
final PreferredSizeWidget? bottom;
|
||||
|
||||
const UiAppBar({
|
||||
super.key,
|
||||
this.title,
|
||||
this.titleWidget,
|
||||
this.leading,
|
||||
this.actions,
|
||||
this.height = kToolbarHeight,
|
||||
this.centerTitle = true,
|
||||
this.onLeadingPressed,
|
||||
this.showBackButton = true,
|
||||
this.bottom,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppBar(
|
||||
title: titleWidget ??
|
||||
(title != null
|
||||
? Text(
|
||||
title!,
|
||||
)
|
||||
: null),
|
||||
leading: leading ??
|
||||
(showBackButton
|
||||
? IconButton(
|
||||
icon: const Icon(UiIcons.chevronLeft, size: 20),
|
||||
onPressed: onLeadingPressed ?? () => Navigator.of(context).pop(),
|
||||
)
|
||||
: null),
|
||||
actions: actions,
|
||||
centerTitle: centerTitle,
|
||||
bottom: bottom,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Size get preferredSize => Size.fromHeight(height + (bottom?.preferredSize.height ?? 0.0));
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../ui_constants.dart';
|
||||
|
||||
/// A custom button widget with different variants and icon support.
|
||||
class UiButton extends StatelessWidget {
|
||||
/// The text to display on the button.
|
||||
final String? text;
|
||||
|
||||
/// Optional custom child widget. If provided, overrides text and icons.
|
||||
final Widget? child;
|
||||
|
||||
/// Callback when the button is tapped.
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
/// Optional leading icon.
|
||||
final IconData? leadingIcon;
|
||||
|
||||
/// Optional trailing icon.
|
||||
final IconData? trailingIcon;
|
||||
|
||||
/// Optional Style
|
||||
final ButtonStyle? style;
|
||||
|
||||
/// The size of the icons. Defaults to 20.
|
||||
final double iconSize;
|
||||
|
||||
/// The size of the button.
|
||||
final UiButtonSize size;
|
||||
|
||||
/// The button widget to use (ElevatedButton, OutlinedButton, or TextButton).
|
||||
final Widget Function(
|
||||
BuildContext context,
|
||||
VoidCallback? onPressed,
|
||||
ButtonStyle? style,
|
||||
Widget child,
|
||||
)
|
||||
buttonBuilder;
|
||||
|
||||
/// Creates a [UiButton] with a custom button builder.
|
||||
const UiButton({
|
||||
super.key,
|
||||
this.text,
|
||||
this.child,
|
||||
required this.buttonBuilder,
|
||||
this.onPressed,
|
||||
this.leadingIcon,
|
||||
this.trailingIcon,
|
||||
this.style,
|
||||
this.iconSize = 20,
|
||||
this.size = UiButtonSize.medium,
|
||||
}) : assert(
|
||||
text != null || child != null,
|
||||
'Either text or child must be provided',
|
||||
);
|
||||
|
||||
/// Creates a primary button using [ElevatedButton].
|
||||
UiButton.primary({
|
||||
super.key,
|
||||
this.text,
|
||||
this.child,
|
||||
this.onPressed,
|
||||
this.leadingIcon,
|
||||
this.trailingIcon,
|
||||
this.style,
|
||||
this.iconSize = 20,
|
||||
this.size = UiButtonSize.medium,
|
||||
}) : buttonBuilder = _elevatedButtonBuilder,
|
||||
assert(
|
||||
text != null || child != null,
|
||||
'Either text or child must be provided',
|
||||
);
|
||||
|
||||
/// Creates a secondary button using [OutlinedButton].
|
||||
UiButton.secondary({
|
||||
super.key,
|
||||
this.text,
|
||||
this.child,
|
||||
this.onPressed,
|
||||
this.leadingIcon,
|
||||
this.trailingIcon,
|
||||
this.style,
|
||||
this.iconSize = 20,
|
||||
this.size = UiButtonSize.medium,
|
||||
}) : buttonBuilder = _outlinedButtonBuilder,
|
||||
assert(
|
||||
text != null || child != null,
|
||||
'Either text or child must be provided',
|
||||
);
|
||||
|
||||
/// Creates a text button using [TextButton].
|
||||
UiButton.text({
|
||||
super.key,
|
||||
this.text,
|
||||
this.child,
|
||||
this.onPressed,
|
||||
this.leadingIcon,
|
||||
this.trailingIcon,
|
||||
this.style,
|
||||
this.iconSize = 20,
|
||||
this.size = UiButtonSize.medium,
|
||||
}) : buttonBuilder = _textButtonBuilder,
|
||||
assert(
|
||||
text != null || child != null,
|
||||
'Either text or child must be provided',
|
||||
);
|
||||
|
||||
@override
|
||||
/// Builds the button UI.
|
||||
Widget build(BuildContext context) {
|
||||
return buttonBuilder(context, onPressed, style, _buildButtonContent());
|
||||
}
|
||||
|
||||
/// Builds the button content with optional leading and trailing icons.
|
||||
Widget _buildButtonContent() {
|
||||
if (child != null) {
|
||||
return child!;
|
||||
}
|
||||
|
||||
// Single icon or text case
|
||||
if (leadingIcon == null && trailingIcon == null) {
|
||||
return Text(text!);
|
||||
}
|
||||
|
||||
if (leadingIcon != null && text == null && trailingIcon == null) {
|
||||
return Icon(leadingIcon, size: iconSize);
|
||||
}
|
||||
|
||||
// Multiple elements case
|
||||
final List<Widget> children = [];
|
||||
|
||||
if (leadingIcon != null) {
|
||||
children.add(Icon(leadingIcon, size: iconSize));
|
||||
children.add(const SizedBox(width: UiConstants.space2));
|
||||
}
|
||||
|
||||
children.add(Text(text!));
|
||||
|
||||
if (trailingIcon != null) {
|
||||
children.add(const SizedBox(width: UiConstants.space2));
|
||||
children.add(Icon(trailingIcon, size: iconSize));
|
||||
}
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
/// Builder for ElevatedButton.
|
||||
static Widget _elevatedButtonBuilder(
|
||||
BuildContext context,
|
||||
VoidCallback? onPressed,
|
||||
ButtonStyle? style,
|
||||
Widget child,
|
||||
) {
|
||||
return ElevatedButton(onPressed: onPressed, style: style, child: child);
|
||||
}
|
||||
|
||||
/// Builder for OutlinedButton.
|
||||
static Widget _outlinedButtonBuilder(
|
||||
BuildContext context,
|
||||
VoidCallback? onPressed,
|
||||
ButtonStyle? style,
|
||||
Widget child,
|
||||
) {
|
||||
return OutlinedButton(onPressed: onPressed, style: style, child: child);
|
||||
}
|
||||
|
||||
/// Builder for TextButton.
|
||||
static Widget _textButtonBuilder(
|
||||
BuildContext context,
|
||||
VoidCallback? onPressed,
|
||||
ButtonStyle? style,
|
||||
Widget child,
|
||||
) {
|
||||
return TextButton(onPressed: onPressed, style: style, child: child);
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the size of a [UiButton].
|
||||
enum UiButtonSize {
|
||||
/// Small button (compact)
|
||||
small,
|
||||
|
||||
/// Medium button (standard)
|
||||
medium,
|
||||
|
||||
/// Large button (prominent)
|
||||
large,
|
||||
}
|
||||
190
apps/mobile/packages/design_system/lib/src/widgets/ui_chip.dart
Normal file
190
apps/mobile/packages/design_system/lib/src/widgets/ui_chip.dart
Normal file
@@ -0,0 +1,190 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../ui_colors.dart';
|
||||
import '../ui_constants.dart';
|
||||
import '../ui_typography.dart';
|
||||
|
||||
/// Sizes for the [UiChip] widget.
|
||||
enum UiChipSize {
|
||||
/// Small size (e.g. for tags in tight spaces).
|
||||
small,
|
||||
|
||||
/// Medium size (default).
|
||||
medium,
|
||||
|
||||
/// Large size (e.g. for standalone filters).
|
||||
large,
|
||||
}
|
||||
|
||||
/// Themes for the [UiChip] widget.
|
||||
enum UiChipVariant {
|
||||
/// Primary style with solid background.
|
||||
primary,
|
||||
|
||||
/// Secondary style with light background.
|
||||
secondary,
|
||||
|
||||
/// Accent style with highlight background.
|
||||
accent,
|
||||
}
|
||||
|
||||
/// A custom chip widget with supports for different sizes, themes, and icons.
|
||||
class UiChip extends StatelessWidget {
|
||||
/// The text label to display.
|
||||
final String label;
|
||||
|
||||
/// The size of the chip. Defaults to [UiChipSize.medium].
|
||||
final UiChipSize size;
|
||||
|
||||
/// The theme variant of the chip. Defaults to [UiChipVariant.secondary].
|
||||
final UiChipVariant variant;
|
||||
|
||||
/// Optional leading icon.
|
||||
final IconData? leadingIcon;
|
||||
|
||||
/// Optional trailing icon.
|
||||
final IconData? trailingIcon;
|
||||
|
||||
/// Callback when the chip is tapped.
|
||||
final VoidCallback? onTap;
|
||||
|
||||
/// Callback when the trailing icon is tapped (e.g. for removal).
|
||||
final VoidCallback? onTrailingIconTap;
|
||||
|
||||
/// Whether the chip is currently selected/active.
|
||||
final bool isSelected;
|
||||
|
||||
/// Creates a [UiChip].
|
||||
const UiChip({
|
||||
super.key,
|
||||
required this.label,
|
||||
this.size = UiChipSize.medium,
|
||||
this.variant = UiChipVariant.secondary,
|
||||
this.leadingIcon,
|
||||
this.trailingIcon,
|
||||
this.onTap,
|
||||
this.onTrailingIconTap,
|
||||
this.isSelected = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final backgroundColor = _getBackgroundColor();
|
||||
final contentColor = _getContentColor();
|
||||
final textStyle = _getTextStyle().copyWith(color: contentColor);
|
||||
final padding = _getPadding();
|
||||
final iconSize = _getIconSize();
|
||||
|
||||
final content = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (leadingIcon != null) ...[
|
||||
Icon(leadingIcon, size: iconSize, color: contentColor),
|
||||
SizedBox(width: _getGap()),
|
||||
],
|
||||
Text(label, style: textStyle),
|
||||
if (trailingIcon != null) ...[
|
||||
SizedBox(width: _getGap()),
|
||||
GestureDetector(
|
||||
onTap: onTrailingIconTap,
|
||||
child: Icon(trailingIcon, size: iconSize, color: contentColor),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: UiConstants.radiusFull,
|
||||
border: _getBorder(),
|
||||
),
|
||||
child: content,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Color _getBackgroundColor() {
|
||||
if (!isSelected && variant == UiChipVariant.primary) {
|
||||
return UiColors.white;
|
||||
}
|
||||
|
||||
switch (variant) {
|
||||
case UiChipVariant.primary:
|
||||
return UiColors.primary;
|
||||
case UiChipVariant.secondary:
|
||||
return UiColors.tagInProgress;
|
||||
case UiChipVariant.accent:
|
||||
return UiColors.accent;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getContentColor() {
|
||||
if (!isSelected && variant == UiChipVariant.primary) {
|
||||
return UiColors.textSecondary;
|
||||
}
|
||||
|
||||
switch (variant) {
|
||||
case UiChipVariant.primary:
|
||||
return UiColors.white;
|
||||
case UiChipVariant.secondary:
|
||||
return UiColors.primary;
|
||||
case UiChipVariant.accent:
|
||||
return UiColors.accentForeground;
|
||||
}
|
||||
}
|
||||
|
||||
TextStyle _getTextStyle() {
|
||||
switch (size) {
|
||||
case UiChipSize.small:
|
||||
return UiTypography.body3r;
|
||||
case UiChipSize.medium:
|
||||
return UiTypography.body2m;
|
||||
case UiChipSize.large:
|
||||
return UiTypography.body1m;
|
||||
}
|
||||
}
|
||||
|
||||
EdgeInsets _getPadding() {
|
||||
switch (size) {
|
||||
case UiChipSize.small:
|
||||
return const EdgeInsets.symmetric(horizontal: 10, vertical: 6);
|
||||
case UiChipSize.medium:
|
||||
return const EdgeInsets.symmetric(horizontal: 12, vertical: 8);
|
||||
case UiChipSize.large:
|
||||
return const EdgeInsets.symmetric(horizontal: 16, vertical: 10);
|
||||
}
|
||||
}
|
||||
|
||||
double _getIconSize() {
|
||||
switch (size) {
|
||||
case UiChipSize.small:
|
||||
return 12;
|
||||
case UiChipSize.medium:
|
||||
return 16;
|
||||
case UiChipSize.large:
|
||||
return 20;
|
||||
}
|
||||
}
|
||||
|
||||
double _getGap() {
|
||||
switch (size) {
|
||||
case UiChipSize.small:
|
||||
return UiConstants.space1;
|
||||
case UiChipSize.medium:
|
||||
return UiConstants.space1 + 2;
|
||||
case UiChipSize.large:
|
||||
return UiConstants.space2;
|
||||
}
|
||||
}
|
||||
|
||||
BoxBorder? _getBorder() {
|
||||
if (!isSelected && variant == UiChipVariant.primary) {
|
||||
return Border.all(color: UiColors.border);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../ui_colors.dart';
|
||||
import '../ui_constants.dart';
|
||||
|
||||
/// A custom icon button with blur effect and different variants.
|
||||
class UiIconButton extends StatelessWidget {
|
||||
/// The icon to display.
|
||||
final IconData icon;
|
||||
|
||||
/// The size of the icon button.
|
||||
final double size;
|
||||
|
||||
/// The size of the icon.
|
||||
final double iconSize;
|
||||
|
||||
/// The background color of the button.
|
||||
final Color backgroundColor;
|
||||
|
||||
/// The color of the icon.
|
||||
final Color iconColor;
|
||||
|
||||
/// Whether to apply blur effect.
|
||||
final bool useBlur;
|
||||
|
||||
/// Callback when the button is tapped.
|
||||
final VoidCallback? onTap;
|
||||
|
||||
/// Creates a [UiIconButton] with custom properties.
|
||||
const UiIconButton({
|
||||
super.key,
|
||||
required this.icon,
|
||||
this.size = 40,
|
||||
this.iconSize = 20,
|
||||
required this.backgroundColor,
|
||||
required this.iconColor,
|
||||
this.useBlur = false,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
/// Creates a primary variant icon button with solid background.
|
||||
const UiIconButton.primary({
|
||||
super.key,
|
||||
required this.icon,
|
||||
this.size = 40,
|
||||
this.iconSize = 20,
|
||||
this.onTap,
|
||||
}) : backgroundColor = UiColors.primary,
|
||||
iconColor = UiColors.white,
|
||||
useBlur = false;
|
||||
|
||||
/// Creates a secondary variant icon button with blur effect.
|
||||
UiIconButton.secondary({
|
||||
super.key,
|
||||
required this.icon,
|
||||
this.size = 40,
|
||||
this.iconSize = 20,
|
||||
this.onTap,
|
||||
}) : backgroundColor = UiColors.primary.withAlpha(96),
|
||||
iconColor = UiColors.primary,
|
||||
useBlur = true;
|
||||
|
||||
@override
|
||||
/// Builds the icon button UI.
|
||||
Widget build(BuildContext context) {
|
||||
final Widget button = Container(
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(color: backgroundColor, shape: BoxShape.circle),
|
||||
child: Icon(icon, color: iconColor, size: iconSize),
|
||||
);
|
||||
|
||||
final Widget content = useBlur
|
||||
? ClipRRect(
|
||||
borderRadius: UiConstants.radiusFull,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: button,
|
||||
),
|
||||
)
|
||||
: button;
|
||||
|
||||
if (onTap != null) {
|
||||
return GestureDetector(onTap: onTap, child: content);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../ui_colors.dart';
|
||||
import '../ui_constants.dart';
|
||||
import '../ui_icons.dart';
|
||||
|
||||
/// A widget that displays a horizontal step indicator with icons.
|
||||
///
|
||||
/// This widget shows a series of circular step indicators connected by lines,
|
||||
/// with different visual states for completed, active, and inactive steps.
|
||||
class UiStepIndicator extends StatelessWidget {
|
||||
/// The list of icons to display for each step.
|
||||
final List<IconData> stepIcons;
|
||||
|
||||
/// The index of the currently active step (0-based).
|
||||
final int currentStep;
|
||||
|
||||
/// Creates a [UiStepIndicator].
|
||||
const UiStepIndicator({
|
||||
super.key,
|
||||
required this.stepIcons,
|
||||
required this.currentStep,
|
||||
});
|
||||
|
||||
@override
|
||||
/// Builds the step indicator UI.
|
||||
Widget build(BuildContext context) {
|
||||
// active step color
|
||||
const Color activeColor = UiColors.primary;
|
||||
// completed step color
|
||||
const Color completedColor = UiColors.textSuccess;
|
||||
// inactive step color
|
||||
const Color inactiveColor = UiColors.iconSecondary;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: UiConstants.space2),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: List.generate(stepIcons.length, (index) {
|
||||
final bool isActive = index == currentStep;
|
||||
final bool isCompleted = index < currentStep;
|
||||
|
||||
Color bgColor;
|
||||
Color iconColor;
|
||||
if (isCompleted) {
|
||||
bgColor = completedColor.withAlpha(24);
|
||||
iconColor = completedColor;
|
||||
} else if (isActive) {
|
||||
bgColor = activeColor.withAlpha(24);
|
||||
iconColor = activeColor;
|
||||
} else {
|
||||
bgColor = inactiveColor.withAlpha(24);
|
||||
iconColor = inactiveColor.withAlpha(128);
|
||||
}
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
isCompleted ? UiIcons.check : stepIcons[index],
|
||||
size: 20,
|
||||
color: iconColor,
|
||||
),
|
||||
),
|
||||
if (index < stepIcons.length - 1)
|
||||
Container(
|
||||
width: 30,
|
||||
height: 2,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space1,
|
||||
),
|
||||
color: isCompleted
|
||||
? completedColor.withAlpha(96)
|
||||
: inactiveColor.withAlpha(96),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../ui_typography.dart';
|
||||
import '../ui_constants.dart';
|
||||
import '../ui_colors.dart';
|
||||
|
||||
/// A custom TextField for the Krow UI design system.
|
||||
///
|
||||
/// This widget combines a label and a [TextField] with consistent styling.
|
||||
class UiTextField extends StatelessWidget {
|
||||
/// The label text to display above the text field.
|
||||
final String? label;
|
||||
|
||||
/// The hint text to display inside the text field when empty.
|
||||
final String? hintText;
|
||||
|
||||
/// Signature for the callback that is called when the text in the field changes.
|
||||
final ValueChanged<String>? onChanged;
|
||||
|
||||
/// The controller for the text field.
|
||||
final TextEditingController? controller;
|
||||
|
||||
/// The type of keyboard to use for editing the text.
|
||||
final TextInputType? keyboardType;
|
||||
|
||||
/// The maximum number of lines for the text field. Defaults to 1.
|
||||
final int? maxLines;
|
||||
|
||||
/// Whether to hide the text being edited (e.g., for passwords). Defaults to false.
|
||||
final bool obscureText;
|
||||
|
||||
/// The type of action button to use for the keyboard.
|
||||
final TextInputAction? textInputAction;
|
||||
|
||||
/// Signature for the callback that is called when the user submits the text field.
|
||||
final ValueChanged<String>? onSubmitted;
|
||||
|
||||
/// Whether the text field should be focused automatically. Defaults to false.
|
||||
final bool autofocus;
|
||||
|
||||
/// Optional input formatters to validate or format the text as it is typed.
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
|
||||
/// Optional prefix icon to display at the start of the text field.
|
||||
final IconData? prefixIcon;
|
||||
|
||||
/// Optional suffix icon to display at the end of the text field.
|
||||
final IconData? suffixIcon;
|
||||
|
||||
/// Optional custom suffix widget to display at the end (e.g., password toggle).
|
||||
final Widget? suffix;
|
||||
|
||||
/// Whether the text field should be read-only.
|
||||
final bool readOnly;
|
||||
|
||||
/// Callback when the text field is tapped.
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const UiTextField({
|
||||
super.key,
|
||||
this.label,
|
||||
this.hintText,
|
||||
this.onChanged,
|
||||
this.controller,
|
||||
this.keyboardType,
|
||||
this.maxLines = 1,
|
||||
this.obscureText = false,
|
||||
this.textInputAction,
|
||||
this.onSubmitted,
|
||||
this.autofocus = false,
|
||||
this.inputFormatters,
|
||||
this.prefixIcon,
|
||||
this.suffixIcon,
|
||||
this.suffix,
|
||||
this.readOnly = false,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (label != null) ...[
|
||||
Text(label!, style: UiTypography.body4m.textSecondary),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
],
|
||||
TextField(
|
||||
controller: controller,
|
||||
onChanged: onChanged,
|
||||
keyboardType: keyboardType,
|
||||
maxLines: maxLines,
|
||||
obscureText: obscureText,
|
||||
textInputAction: textInputAction,
|
||||
onSubmitted: onSubmitted,
|
||||
autofocus: autofocus,
|
||||
inputFormatters: inputFormatters,
|
||||
readOnly: readOnly,
|
||||
onTap: onTap,
|
||||
style: UiTypography.body1r.textPrimary,
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText,
|
||||
prefixIcon: prefixIcon != null
|
||||
? Icon(prefixIcon, size: 20, color: UiColors.iconSecondary)
|
||||
: null,
|
||||
suffixIcon: suffixIcon != null
|
||||
? Icon(suffixIcon, size: 20, color: UiColors.iconSecondary)
|
||||
: suffix,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user