* feat(widgets): add slate design input components Replace WnTextFormField with new slate design input widgets: - WnInput: Single-line text input with optional label, helper text, inline action, and trailing action support - WnInputPassword: Password input with visibility toggle, scan QR button, and paste/clear actions - WnInputTextArea: Multi-line text input with configurable minimum height based on size variant All components support two sizes (44px and 56px) matching the Figma design system, with consistent typography (Manrope Medium 14px) and semantic colors. Closes #65 * fix: add .toInt() to clamp result and improve password input test coverage - Fix type error in wn_copyable_field.dart (clamp returns num, need int) - Add tests for labelHelpIcon callback in WnInputPassword - All input widgets now have 100% test coverage * fix: improve input widgets and test reliability - Add obscurable to useEffect deps in WnCopyableField - Use .h for height sizing in WnCopyableField Container - Add key to password field container for testability - Add second toggle check in visibility test - Update size tests to assert actual rendered heights - Fix mountWidget/mountStackedWidget to call setUpTestView * fix: improve WnInputPassword responsive sizing and disabled state - Use backgroundSecondary for disabled background (visual distinction) - Split inlineActionSize into separate width (.w) and height (.h) vars - Apply same pattern to _buildInlineAction and _buildTrailingAction - Fix labelHelpIcon SizedBox to use .h for height * fix(widgets): align input background color with Figma design Remove redundant ternary for background color in WnInput and WnInputPassword. Per Figma design, both enabled and disabled states use backgroundPrimary - disabled state is distinguished by text color. * refactor(widgets): clean up WnInput code - Remove redundant Padding with EdgeInsets.all(0.w) around help icon - Use spread operator to combine inlineAction SizedBox and Gap into single conditional block * refactor(widgets): improve WnInput tap target and deduplicate styles - Add HitTestBehavior.opaque to label help icon GestureDetector so full 18.w SizedBox is tappable, not just the 14.w icon - Extract _baseInfoTextStyle getter to deduplicate TextStyle in _buildHelperText and _buildErrorText methods * feat(widgets): add focus/hover states and disable interactions for WnInput - Convert WnInput to HookWidget to track focus and hover state - Add _getBorderColor method that computes border based on enabled, focused, hovered, and error states - Add MouseRegion for hover detection and Focus wrapper for focus tracking - Wrap inlineAction and trailingAction with IgnorePointer when disabled to prevent interactions on disabled inputs * fix(widgets): always register MouseRegion callbacks in WnInput Keep onEnter and onExit callbacks always registered but change behavior: - onEnter only sets hover state when enabled - onExit always clears hover state to prevent stale state on disabled widgets * fix(widgets): use backgroundSecondary for disabled WnInputTextArea * fix(widgets): use .h for height values in WnInput components Per AGENTS.md guidelines, use .w for width-based values and .h for height-based values with flutter_screenutil. * test(widgets): add hover state tests for WnInput to achieve 100% coverage * test(widgets): improve WnInput tests to verify actual visual behavior - Add 'input_field_container' key to WnInput for testability - Update size tests to verify actual rendered height (56px vs 44px) - Update hover tests to verify border color changes: - tertiary -> secondary on hover - secondary -> tertiary on exit - tertiary stays tertiary when disabled and hovered * Update formatting * refactor(widgets): improve input widget consistency and accessibility - Extract shared _baseInfoTextStyle in WnInputPassword for helper/error text - Add HitTestBehavior.opaque to labelHelpIcon GestureDetector for full tap area - Gate inline action buttons by enabled state in WnInputPassword - Fix height unit from .w to .h in WnInputTextArea help icon SizedBox - Add height assertions to WnInputTextArea size tests * fix(widgets): use correct semantic color for error text Change error text color from fillDestructive to backgroundContentDestructive to match Figma design specifications. Fixes PR #115 review comments.
111 lines
3.1 KiB
Dart
111 lines
3.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart' show Consumer, ProviderScope;
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart' show ScreenUtilInit;
|
|
import 'package:flutter_test/flutter_test.dart' show WidgetTester, addTearDown;
|
|
import 'package:sloth/l10n/generated/app_localizations.dart';
|
|
import 'package:sloth/routes.dart';
|
|
|
|
const testDesignSize = Size(390, 844);
|
|
|
|
void setUpTestView(WidgetTester tester) {
|
|
tester.view.physicalSize = testDesignSize;
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.reset);
|
|
}
|
|
|
|
const _localizationsDelegates = [
|
|
AppLocalizations.delegate,
|
|
GlobalMaterialLocalizations.delegate,
|
|
GlobalWidgetsLocalizations.delegate,
|
|
GlobalCupertinoLocalizations.delegate,
|
|
];
|
|
|
|
Future<void> mountTestApp(
|
|
WidgetTester tester, {
|
|
List overrides = const [],
|
|
}) async {
|
|
setUpTestView(tester);
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [...overrides],
|
|
child: ScreenUtilInit(
|
|
designSize: testDesignSize,
|
|
builder: (_, _) => Consumer(
|
|
builder: (context, ref, _) {
|
|
return MaterialApp.router(
|
|
routerConfig: Routes.build(ref),
|
|
locale: const Locale('en'),
|
|
localizationsDelegates: _localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<T Function()> mountHook<T>(
|
|
WidgetTester tester,
|
|
T Function() useHook,
|
|
) async {
|
|
late T result;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
locale: const Locale('en'),
|
|
localizationsDelegates: _localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: _HookTestWidget<T>(useHook, (r) => result = r),
|
|
),
|
|
);
|
|
return () => result;
|
|
}
|
|
|
|
class _HookTestWidget<T> extends HookWidget {
|
|
const _HookTestWidget(this.useHook, this.onBuild);
|
|
final T Function() useHook;
|
|
final void Function(T) onBuild;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
onBuild(useHook());
|
|
return const SizedBox();
|
|
}
|
|
}
|
|
|
|
Future<void> mountWidget(Widget child, WidgetTester tester) async {
|
|
setUpTestView(tester);
|
|
final widget = ProviderScope(
|
|
child: ScreenUtilInit(
|
|
designSize: testDesignSize,
|
|
builder: (_, _) {
|
|
return MaterialApp(
|
|
locale: const Locale('en'),
|
|
localizationsDelegates: _localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: Scaffold(body: child),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
await tester.pumpWidget(widget);
|
|
}
|
|
|
|
Future<void> mountStackedWidget(Widget child, WidgetTester tester) async {
|
|
setUpTestView(tester);
|
|
final widget = ScreenUtilInit(
|
|
designSize: testDesignSize,
|
|
builder: (_, _) {
|
|
return MaterialApp(
|
|
locale: const Locale('en'),
|
|
localizationsDelegates: _localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: Scaffold(body: Stack(children: [child])),
|
|
);
|
|
},
|
|
);
|
|
await tester.pumpWidget(widget);
|
|
}
|