kasy-cli 1.19.3 → 1.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +11 -3
  2. package/bin/kasy.js +1 -0
  3. package/lib/commands/new.js +87 -37
  4. package/lib/commands/run.js +14 -0
  5. package/lib/scaffold/backends/api/patch/lib/core/data/api/meta_ads_api.dart +1 -1
  6. package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
  7. package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +1 -1
  8. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +4 -5
  9. package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +1 -1
  10. package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_vote_api.dart +1 -1
  11. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +1 -1
  12. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/providers/llm_chat_notifier.dart +317 -0
  13. package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +40 -1
  14. package/lib/scaffold/backends/api/patch/lib/features/notifications/api/entities/notifications_entity.dart +2 -0
  15. package/lib/scaffold/backends/api/patch/lib/features/onboarding/api/entities/user_info_entity.dart +1 -1
  16. package/lib/scaffold/backends/api/patch/lib/features/subscription/api/entities/subscription_entity.dart +1 -1
  17. package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -2
  18. package/lib/scaffold/backends/api/pubspec.yaml.tpl +2 -0
  19. package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +11 -8
  20. package/lib/scaffold/backends/firebase/setup-from-scratch.js +91 -2
  21. package/lib/scaffold/backends/supabase/deploy.js +56 -3
  22. package/lib/scaffold/backends/supabase/patch/lib/core/data/api/storage_api.dart +5 -11
  23. package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +2 -2
  24. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +31 -1
  25. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/api/entities/user_info_entity.dart +1 -1
  26. package/lib/scaffold/backends/supabase/patch/lib/features/subscription/api/entities/subscription_entity.dart +1 -1
  27. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +2 -0
  28. package/lib/scaffold/catalog.js +2 -2
  29. package/lib/scaffold/engine.js +5 -0
  30. package/lib/scaffold/generate.js +23 -3
  31. package/lib/scaffold/shared/generator-utils.js +303 -56
  32. package/lib/scaffold/shared/post-build.js +11 -0
  33. package/lib/utils/i18n/messages-en.js +6 -1
  34. package/lib/utils/i18n/messages-es.js +6 -1
  35. package/lib/utils/i18n/messages-pt.js +6 -1
  36. package/package.json +1 -1
  37. package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
  38. package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
  39. package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
  40. package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
  41. package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
  42. package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
  43. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
  44. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
  45. package/templates/firebase/lib/components/kasy_date_picker.dart +14 -8
  46. package/templates/firebase/lib/components/kasy_sidebar_pro.dart +1150 -0
  47. package/templates/firebase/lib/components/kasy_tabs.dart +156 -43
  48. package/templates/firebase/lib/components/kasy_text_field.dart +37 -34
  49. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +13 -82
  50. package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +6 -102
  51. package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +8 -1
  52. package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +433 -243
  53. package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +198 -83
  54. package/templates/firebase/lib/core/icons/kasy_icons.dart +1 -0
  55. package/templates/firebase/lib/core/states/user_state_notifier.dart +8 -10
  56. package/templates/firebase/lib/core/theme/colors.dart +6 -2
  57. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +119 -19
  58. package/templates/firebase/lib/core/widgets/kasy_hover.dart +68 -27
  59. package/templates/firebase/lib/features/home/home_components_page.dart +11 -14
  60. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +121 -66
  61. package/templates/firebase/lib/features/home/home_page.dart +7 -8
  62. package/templates/firebase/lib/features/settings/settings_page.dart +27 -146
  63. package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +16 -3
  64. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +22 -5
  65. package/templates/firebase/lib/i18n/en.i18n.json +3 -1
  66. package/templates/firebase/lib/i18n/es.i18n.json +3 -1
  67. package/templates/firebase/lib/i18n/pt.i18n.json +3 -1
  68. package/templates/firebase/lib/router.dart +60 -0
  69. package/templates/firebase/pubspec.yaml +6 -4
  70. package/templates/firebase/test/core/bottom_menu/detail_route_menu_test.dart +57 -0
  71. package/templates/firebase/web/index.html +7 -17
  72. package/lib/scaffold/backends/api/patch/lib/core/rating/widgets/review_popup.dart +0 -211
  73. package/lib/scaffold/backends/api/patch/lib/features/notifications/providers/models/notification.dart +0 -185
  74. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
  75. package/lib/scaffold/backends/api/patch/lib/main.dart +0 -275
  76. package/lib/scaffold/backends/api/patch/lib/router.dart +0 -133
  77. package/lib/scaffold/backends/supabase/patch/lib/core/rating/widgets/review_popup.dart +0 -211
  78. package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/ui/component/add_feature_form.dart +0 -199
  79. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/providers/models/notification.dart +0 -174
  80. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
  81. package/lib/scaffold/backends/supabase/patch/lib/main.dart +0 -307
  82. package/lib/scaffold/backends/supabase/patch/lib/router.dart +0 -133
  83. package/templates/firebase/lib/firebase_options.dart +0 -75
@@ -7,18 +7,33 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
7
7
  import 'package:kasy_kit/core/haptics/haptic_feedback_notifier.dart';
8
8
  import 'package:kasy_kit/core/theme/theme.dart';
9
9
 
10
- /// Universal hover/press wrapper — works on any widget shape.
10
+ /// Universal hover/press wrapper — works on any widget shape, any platform.
11
11
  ///
12
- /// Native: animated background fill on press + haptic (no Material ripple).
13
- /// The pressed state lasts at least [_kMinPressDuration] so it is visible
14
- /// even on quick taps.
12
+ /// **Press feedback:** animated background fill + optional haptic. No Material
13
+ /// ripple. The pressed state persists for at least [_kMinPressDuration] so
14
+ /// it is always visible, even on quick taps.
15
15
  ///
16
- /// Web: pointer cursor + subtle background on hover, slightly darker on press.
16
+ /// **Hover feedback:** subtle overlay on pointer-capable devices (web,
17
+ /// desktop, trackpad). Has no visible effect on touch-only devices.
18
+ ///
19
+ /// **Cursor:** [SystemMouseCursors.click] is set automatically on all
20
+ /// platforms that support pointer input.
21
+ ///
22
+ /// ### Customising colours
23
+ ///
24
+ /// - [pressColor] — tint base for the press overlay (defaults to
25
+ /// `onSurface`). Pass the surface accent colour (e.g. `primary`) to avoid
26
+ /// a mismatched gray cast on tinted backgrounds.
27
+ ///
28
+ /// - [hoverColor] — exact solid background colour shown while the pointer is
29
+ /// over the widget. When set, **replaces** the default semi-transparent
30
+ /// overlay with a solid fill. Use this on navigation items where hover and
31
+ /// selected states must look identical (same bg, bolder text/icon in the
32
+ /// child widget signals selection). When null, falls back to a subtle
33
+ /// [pressColor]-tinted overlay.
17
34
  ///
18
35
  /// Contrast with [KasyPressableDepth]: this component uses a background fill
19
- /// while [KasyPressableDepth] uses a scale animation. Use this one for
20
- /// surfaces that already have their own shape (list rows, cards, menu items,
21
- /// custom buttons); use [KasyPressableDepth] when you want a "push-in" feel.
36
+ /// while [KasyPressableDepth] uses a scale/depth animation.
22
37
  class KasyHover extends ConsumerStatefulWidget {
23
38
  const KasyHover({
24
39
  super.key,
@@ -29,6 +44,7 @@ class KasyHover extends ConsumerStatefulWidget {
29
44
  this.margin,
30
45
  this.semanticLabel,
31
46
  this.hapticEnabled = true,
47
+ this.hoverColor,
32
48
  this.pressColor,
33
49
  });
34
50
 
@@ -51,15 +67,24 @@ class KasyHover extends ConsumerStatefulWidget {
51
67
  /// Haptic on tap (automatically ignored on web).
52
68
  final bool hapticEnabled;
53
69
 
54
- /// Tint color for the hover/press overlay.
70
+ /// Exact background colour used while the pointer hovers over the widget.
71
+ ///
72
+ /// When non-null, this solid colour replaces the default semi-transparent
73
+ /// overlay during hover. Ideal for navigation items: pass the same colour
74
+ /// used for the selected state so hover and active look identical — the
75
+ /// child widget is responsible for rendering the selected indicator (e.g.
76
+ /// bolder text, accent icon colour).
77
+ ///
78
+ /// When null, a subtle [pressColor]-tinted overlay is used instead.
79
+ final Color? hoverColor;
80
+
81
+ /// Tint base for the press overlay.
55
82
  ///
56
83
  /// When null, falls back to [ColorScheme.onSurface] — a neutral dark/light
57
84
  /// overlay suited for plain surfaces.
58
85
  ///
59
- /// Pass the surface's accent color (e.g. [KasyColors.primary]) when the
60
- /// background is already tinted so the feedback stays on-palette and avoids
61
- /// a mismatched gray cast (e.g. a primary-tinted button should deepen with
62
- /// primary, not darken with neutral gray).
86
+ /// Pass the surface's accent colour (e.g. [KasyColors.primary]) when the
87
+ /// background is already tinted so the feedback stays on-palette.
63
88
  final Color? pressColor;
64
89
 
65
90
  @override
@@ -85,7 +110,21 @@ class _KasyHoverState extends ConsumerState<KasyHover> {
85
110
  final bool isDark = Theme.of(context).brightness == Brightness.dark;
86
111
  final Color base = widget.pressColor ?? context.colors.onSurface;
87
112
  if (_pressed) return base.withValues(alpha: isDark ? 0.04 : 0.10);
88
- if (_hovered) return base.withValues(alpha: isDark ? 0.02 : 0.06);
113
+ if (_hovered) {
114
+ // Solid hover colour — used for nav items where hover must exactly
115
+ // match the selected background.
116
+ if (widget.hoverColor != null) return widget.hoverColor!;
117
+ return base.withValues(alpha: isDark ? 0.02 : 0.06);
118
+ }
119
+ // IMPORTANT: when hoverColor is set, the resting state must be the same
120
+ // colour at alpha=0 (not Colors.transparent = 0x00000000 black).
121
+ // AnimatedContainer lerps between the two values — lerping from black-
122
+ // transparent to a light solid colour passes through dark intermediates,
123
+ // causing a visible flash. Lerping from the same hue at alpha=0 to
124
+ // alpha=1 only changes opacity → no dark artefact.
125
+ if (widget.hoverColor != null) {
126
+ return widget.hoverColor!.withValues(alpha: 0);
127
+ }
89
128
  return Colors.transparent;
90
129
  }
91
130
 
@@ -136,19 +175,21 @@ class _KasyHoverState extends ConsumerState<KasyHover> {
136
175
  child: content,
137
176
  );
138
177
 
139
- if (kIsWeb) {
140
- interactive = MouseRegion(
141
- cursor: SystemMouseCursors.click,
142
- onEnter: (_) => setState(() => _hovered = true),
143
- onExit: (_) {
144
- setState(() {
145
- _hovered = false;
146
- _pressed = false;
147
- });
148
- },
149
- child: interactive,
150
- );
151
- }
178
+ // MouseRegion is active on all platforms:
179
+ // - Web / desktop / trackpad: fires onEnter/onExit for real hover effects.
180
+ // - Touch-only (iOS/Android): pointer events never fire, zero overhead.
181
+ // The click cursor is also set universally so desktop feels native.
182
+ interactive = MouseRegion(
183
+ cursor: SystemMouseCursors.click,
184
+ onEnter: (_) => setState(() => _hovered = true),
185
+ onExit: (_) {
186
+ setState(() {
187
+ _hovered = false;
188
+ _pressed = false;
189
+ });
190
+ },
191
+ child: interactive,
192
+ );
152
193
 
153
194
  if (widget.semanticLabel != null) {
154
195
  interactive = Semantics(
@@ -1,11 +1,10 @@
1
1
  import 'package:flutter/material.dart';
2
+ import 'package:go_router/go_router.dart';
2
3
  import 'package:kasy_kit/components/kasy_app_bar.dart';
3
- import 'package:kasy_kit/core/bottom_menu/bart_inner_paths.dart';
4
4
  import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
5
5
  import 'package:kasy_kit/core/theme/theme.dart';
6
6
  import 'package:kasy_kit/core/widgets/kasy_hover.dart';
7
7
  import 'package:kasy_kit/core/widgets/kasy_scroll_behavior.dart';
8
- import 'package:kasy_kit/features/home/home_components_preview_page.dart';
9
8
  import 'package:kasy_kit/features/home/home_components_preview_registry.dart';
10
9
 
11
10
  /// UI-kit catalog, reachable from the home dashboard.
@@ -66,22 +65,17 @@ class HomeComponentsPage extends StatelessWidget {
66
65
  onTap: () {
67
66
  KasyHaptics.selection(context);
68
67
  if (row.name == 'Design System') {
69
- Navigator.of(context).pushNamed(
70
- bartInnerPath('home', 'design-system'),
71
- );
68
+ context.push('/design-system');
72
69
  return;
73
70
  }
74
- final ComponentPreviewDefinition? definition =
75
- getComponentPreviewDefinition(row.name);
76
- if (definition == null) {
71
+ // Skip rows without a preview yet (registry returns
72
+ // null); otherwise open /components/:name, which the
73
+ // router resolves back to this component's preview.
74
+ if (getComponentPreviewDefinition(row.name) == null) {
77
75
  return;
78
76
  }
79
- Navigator.of(context).pushNamed(
80
- bartInnerPath('home', 'components-preview'),
81
- arguments: HomeComponentPreviewRouteExtra(
82
- title: definition.title,
83
- variants: definition.variants,
84
- ),
77
+ context.push(
78
+ '/components/${Uri.encodeComponent(row.name)}',
85
79
  );
86
80
  },
87
81
  child: Padding(
@@ -189,6 +183,7 @@ const Set<String> _kReadyComponents = <String>{
189
183
  'TextArea',
190
184
  'TextField',
191
185
  'Sidebar',
186
+ 'Sidebar Pro',
192
187
  'Skeleton',
193
188
  'SwipeAction',
194
189
  'TextFieldOTP',
@@ -211,6 +206,7 @@ const Set<String> _kWebReadyComponents = <String>{
211
206
  'Dialog',
212
207
  'Hover',
213
208
  'Sidebar',
209
+ 'Sidebar Pro',
214
210
  'Skeleton',
215
211
  'SwipeAction',
216
212
  'TextArea',
@@ -241,6 +237,7 @@ const List<_CatalogRow> _kCatalog = [
241
237
  _CatalogRow('Dialog'),
242
238
  _CatalogRow('Hover'),
243
239
  _CatalogRow('Sidebar'),
240
+ _CatalogRow('Sidebar Pro'),
244
241
  _CatalogRow('Skeleton'),
245
242
  _CatalogRow('SwipeAction'),
246
243
  _CatalogRow('TextArea'),
@@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart' show kIsWeb;
3
3
  import 'package:flutter/material.dart';
4
4
  import 'package:flutter/services.dart';
5
5
  import 'package:kasy_kit/components/components.dart';
6
+ import 'package:kasy_kit/components/kasy_sidebar_pro.dart';
6
7
  import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
7
8
  import 'package:kasy_kit/core/theme/theme.dart';
8
9
  import 'package:kasy_kit/core/widgets/kasy_hover.dart';
@@ -244,10 +245,6 @@ ComponentPreviewDefinition? getComponentPreviewDefinition(
244
245
  label: 'Range selection',
245
246
  builder: _buildDatePickerRange,
246
247
  ),
247
- ComponentPreviewVariant(
248
- label: 'Multi-month',
249
- builder: _buildDatePickerMultiMonth,
250
- ),
251
248
  ComponentPreviewVariant(
252
249
  label: 'Date formats',
253
250
  builder: _buildDatePickerFormats,
@@ -260,6 +257,13 @@ ComponentPreviewDefinition? getComponentPreviewDefinition(
260
257
  label: 'Field states',
261
258
  builder: _buildDatePickerFieldStates,
262
259
  ),
260
+ // Multi-month last — it's a desktop-only opt-in layout and the
261
+ // preview itself falls back to an explainer card on mobile, so
262
+ // keeping it at the end avoids burying the main use cases.
263
+ ComponentPreviewVariant(
264
+ label: 'Multi-month',
265
+ builder: _buildDatePickerMultiMonth,
266
+ ),
263
267
  ],
264
268
  );
265
269
  case 'Dialog':
@@ -513,6 +517,20 @@ ComponentPreviewDefinition? getComponentPreviewDefinition(
513
517
  ),
514
518
  ],
515
519
  );
520
+ case 'Sidebar Pro':
521
+ return const ComponentPreviewDefinition(
522
+ title: 'Sidebar Pro',
523
+ variants: [
524
+ ComponentPreviewVariant(
525
+ label: 'Expanded',
526
+ builder: _buildSidebarProExpanded,
527
+ ),
528
+ ComponentPreviewVariant(
529
+ label: 'Collapsed',
530
+ builder: _buildSidebarProCollapsed,
531
+ ),
532
+ ],
533
+ );
516
534
  case 'Skeleton':
517
535
  return const ComponentPreviewDefinition(
518
536
  title: 'Skeleton',
@@ -685,6 +703,91 @@ class _SidebarPreviewState extends State<_SidebarPreview> {
685
703
  }
686
704
 
687
705
 
706
+ // ─────────────────────────────────────────────────────────────────────────────
707
+ // Sidebar Pro — interactive preview
708
+ // ─────────────────────────────────────────────────────────────────────────────
709
+
710
+ Widget _buildSidebarProExpanded(BuildContext context) =>
711
+ const _SidebarProPreview(initiallyCollapsed: false);
712
+
713
+ Widget _buildSidebarProCollapsed(BuildContext context) =>
714
+ const _SidebarProPreview(initiallyCollapsed: true);
715
+
716
+ class _SidebarProPreview extends StatelessWidget {
717
+ const _SidebarProPreview({required this.initiallyCollapsed});
718
+
719
+ final bool initiallyCollapsed;
720
+
721
+ void _open(BuildContext context, {required bool fromEnd}) {
722
+ showGeneralDialog<void>(
723
+ context: context,
724
+ barrierDismissible: true,
725
+ barrierLabel: 'Dismiss',
726
+ barrierColor: Colors.black.withValues(alpha: 0.42),
727
+ transitionDuration: const Duration(milliseconds: 290),
728
+ pageBuilder: (ctx, anim, secAnim) => Align(
729
+ alignment: fromEnd ? Alignment.centerRight : Alignment.centerLeft,
730
+ child: SizedBox(
731
+ height: double.infinity,
732
+ child: KasySidebarPro(
733
+ initiallyCollapsed: initiallyCollapsed,
734
+ side: fromEnd
735
+ ? KasySidebarProSide.right
736
+ : KasySidebarProSide.left,
737
+ ),
738
+ ),
739
+ ),
740
+ transitionBuilder: (ctx, anim, secAnim, child) {
741
+ final curved = CurvedAnimation(
742
+ parent: anim,
743
+ curve: Curves.easeOutCubic,
744
+ );
745
+ return SlideTransition(
746
+ position: Tween<Offset>(
747
+ begin: Offset(fromEnd ? 1.0 : -1.0, 0),
748
+ end: Offset.zero,
749
+ ).animate(curved),
750
+ child: FadeTransition(
751
+ opacity: Tween<double>(begin: 0.85, end: 1.0).animate(curved),
752
+ child: child,
753
+ ),
754
+ );
755
+ },
756
+ );
757
+ }
758
+
759
+ @override
760
+ Widget build(BuildContext context) {
761
+ return Column(
762
+ mainAxisSize: MainAxisSize.min,
763
+ crossAxisAlignment: CrossAxisAlignment.stretch,
764
+ children: [
765
+ Padding(
766
+ padding: const EdgeInsets.symmetric(horizontal: KasySpacing.md),
767
+ child: _previewIntrinsicLaunch(
768
+ KasyButton(
769
+ label: 'Open Left',
770
+ variant: KasyButtonVariant.soft,
771
+ onPressed: () => _open(context, fromEnd: false),
772
+ ),
773
+ ),
774
+ ),
775
+ const SizedBox(height: KasySpacing.sm),
776
+ Padding(
777
+ padding: const EdgeInsets.symmetric(horizontal: KasySpacing.md),
778
+ child: _previewIntrinsicLaunch(
779
+ KasyButton(
780
+ label: 'Open Right',
781
+ variant: KasyButtonVariant.soft,
782
+ onPressed: () => _open(context, fromEnd: true),
783
+ ),
784
+ ),
785
+ ),
786
+ ],
787
+ );
788
+ }
789
+ }
790
+
688
791
  // ─────────────────────────────────────────────────────────────────────────────
689
792
  // ─────────────────────────────────────────────────────────────────────────────
690
793
  // Tabs — interactive demos
@@ -2627,29 +2730,18 @@ Widget _buildTextFieldCharacterLimit(BuildContext context) {
2627
2730
  }
2628
2731
 
2629
2732
  Widget _buildTextFieldVariantsVariant(BuildContext context) {
2630
- final TextStyle labelStyle =
2631
- context.textTheme.labelSmall?.copyWith(
2632
- color: context.colors.muted,
2633
- letterSpacing: 1.2,
2634
- fontWeight: FontWeight.w700,
2635
- ) ??
2636
- const TextStyle();
2637
- return Column(
2733
+ return const Column(
2638
2734
  mainAxisSize: MainAxisSize.min,
2639
2735
  crossAxisAlignment: CrossAxisAlignment.stretch,
2640
2736
  children: [
2641
- Text('PRIMARY', style: labelStyle),
2642
- const SizedBox(height: KasySpacing.sm),
2643
- const KasyTextField(
2737
+ KasyTextField(
2644
2738
  label: 'Primary Variant',
2645
2739
  hint: 'Primary style input',
2646
2740
  description: 'Default variant with primary styling',
2647
2741
  contentType: KasyTextFieldContentType.email,
2648
2742
  ),
2649
- const SizedBox(height: KasySpacing.lg),
2650
- Text('SECONDARY', style: labelStyle),
2651
- const SizedBox(height: KasySpacing.sm),
2652
- const KasyTextField(
2743
+ SizedBox(height: KasySpacing.lg),
2744
+ KasyTextField(
2653
2745
  label: 'Secondary Variant',
2654
2746
  hint: 'Secondary style input',
2655
2747
  description: 'Secondary variant for surfaces',
@@ -2661,39 +2753,26 @@ Widget _buildTextFieldVariantsVariant(BuildContext context) {
2661
2753
  }
2662
2754
 
2663
2755
  Widget _buildTextFieldStates(BuildContext context) {
2664
- final TextStyle labelStyle =
2665
- context.textTheme.labelSmall?.copyWith(
2666
- color: context.colors.muted,
2667
- letterSpacing: 1.2,
2668
- fontWeight: FontWeight.w700,
2669
- ) ??
2670
- const TextStyle();
2671
- return Column(
2756
+ return const Column(
2672
2757
  mainAxisSize: MainAxisSize.min,
2673
2758
  crossAxisAlignment: CrossAxisAlignment.stretch,
2674
2759
  children: [
2675
- Text('DEFAULT', style: labelStyle),
2676
- const SizedBox(height: KasySpacing.sm),
2677
- const KasyTextField(
2760
+ KasyTextField(
2678
2761
  label: 'Default State',
2679
2762
  hint: 'Enter your email',
2680
2763
  description: 'Normal input state',
2681
2764
  contentType: KasyTextFieldContentType.email,
2682
2765
  ),
2683
- const SizedBox(height: KasySpacing.lg),
2684
- Text('DISABLED', style: labelStyle),
2685
- const SizedBox(height: KasySpacing.sm),
2686
- const KasyTextField(
2766
+ SizedBox(height: KasySpacing.lg),
2767
+ KasyTextField(
2687
2768
  label: 'Disabled State',
2688
2769
  hint: 'Read only value',
2689
2770
  description: 'TextField is disabled and cannot be edited',
2690
2771
  enabled: false,
2691
2772
  contentType: KasyTextFieldContentType.email,
2692
2773
  ),
2693
- const SizedBox(height: KasySpacing.lg),
2694
- Text('INVALID', style: labelStyle),
2695
- const SizedBox(height: KasySpacing.sm),
2696
- const KasyTextField(
2774
+ SizedBox(height: KasySpacing.lg),
2775
+ KasyTextField(
2697
2776
  label: 'Invalid State',
2698
2777
  hint: 'Enter your email',
2699
2778
  errorText: 'Please enter a valid email address',
@@ -2721,29 +2800,18 @@ Widget _buildTextAreaWithLabelDescription(BuildContext context) {
2721
2800
  }
2722
2801
 
2723
2802
  Widget _buildTextAreaStates(BuildContext context) {
2724
- final TextStyle labelStyle =
2725
- context.textTheme.labelSmall?.copyWith(
2726
- color: context.colors.muted,
2727
- letterSpacing: 1.2,
2728
- fontWeight: FontWeight.w700,
2729
- ) ??
2730
- const TextStyle();
2731
- return Column(
2803
+ return const Column(
2732
2804
  mainAxisSize: MainAxisSize.min,
2733
2805
  crossAxisAlignment: CrossAxisAlignment.stretch,
2734
2806
  children: [
2735
- Text('DISABLED', style: labelStyle),
2736
- const SizedBox(height: KasySpacing.sm),
2737
- const KasyTextArea(
2807
+ KasyTextArea(
2738
2808
  label: 'Disabled State',
2739
2809
  hint: 'Read only value',
2740
2810
  description: 'Text area is disabled and cannot be edited',
2741
2811
  enabled: false,
2742
2812
  ),
2743
- const SizedBox(height: KasySpacing.lg),
2744
- Text('INVALID', style: labelStyle),
2745
- const SizedBox(height: KasySpacing.sm),
2746
- const KasyTextArea(
2813
+ SizedBox(height: KasySpacing.lg),
2814
+ KasyTextArea(
2747
2815
  label: 'Invalid State',
2748
2816
  hint: 'Enter your message',
2749
2817
  isInvalid: true,
@@ -4860,19 +4928,10 @@ class _TextFieldWithIconContentState extends State<_TextFieldWithIconContent> {
4860
4928
 
4861
4929
  @override
4862
4930
  Widget build(BuildContext context) {
4863
- final TextStyle labelStyle =
4864
- context.textTheme.labelSmall?.copyWith(
4865
- color: context.colors.muted,
4866
- letterSpacing: 1.2,
4867
- fontWeight: FontWeight.w700,
4868
- ) ??
4869
- const TextStyle();
4870
4931
  return Column(
4871
4932
  mainAxisSize: MainAxisSize.min,
4872
4933
  crossAxisAlignment: CrossAxisAlignment.stretch,
4873
4934
  children: [
4874
- Text('PHONE', style: labelStyle),
4875
- const SizedBox(height: KasySpacing.sm),
4876
4935
  const KasyTextField(
4877
4936
  label: 'Phone',
4878
4937
  hint: '5550000000',
@@ -4880,16 +4939,12 @@ class _TextFieldWithIconContentState extends State<_TextFieldWithIconContent> {
4880
4939
  contentType: KasyTextFieldContentType.phone,
4881
4940
  ),
4882
4941
  const SizedBox(height: KasySpacing.lg),
4883
- Text('PASSWORD', style: labelStyle),
4884
- const SizedBox(height: KasySpacing.sm),
4885
4942
  const KasyTextField(
4886
4943
  label: 'Password',
4887
4944
  hint: 'Enter your password',
4888
4945
  contentType: KasyTextFieldContentType.password,
4889
4946
  ),
4890
4947
  const SizedBox(height: KasySpacing.lg),
4891
- Text('MESSAGE', style: labelStyle),
4892
- const SizedBox(height: KasySpacing.sm),
4893
4948
  KasyTextField(
4894
4949
  controller: _messageController,
4895
4950
  label: 'Message',
@@ -1,7 +1,7 @@
1
1
  import 'package:flutter/material.dart';
2
2
  import 'package:flutter_riverpod/flutter_riverpod.dart';
3
+ import 'package:go_router/go_router.dart';
3
4
  import 'package:kasy_kit/components/kasy_app_bar.dart';
4
- import 'package:kasy_kit/core/bottom_menu/bart_inner_paths.dart';
5
5
  import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
6
6
  import 'package:kasy_kit/core/states/components/maybe_ask_rating.dart';
7
7
  import 'package:kasy_kit/core/states/components/maybe_show_update_bottom_sheet.dart';
@@ -13,7 +13,7 @@ import 'package:kasy_kit/features/notifications/shared/att_permission.dart';
13
13
  import 'package:kasy_kit/features/subscription/shared/maybeshow_premium.dart';
14
14
  import 'package:kasy_kit/i18n/translations.g.dart';
15
15
 
16
- /// Home hub with dashboard cards; features live under [HomeFeaturesPage].
16
+ /// Home hub with dashboard cards linking to the Features and Components screens.
17
17
  class HomePage extends ConsumerWidget {
18
18
  const HomePage({super.key});
19
19
 
@@ -59,9 +59,10 @@ class HomePage extends ConsumerWidget {
59
59
  gradient: _featuresGradient(context, isDark),
60
60
  onOpen: () {
61
61
  KasyHaptics.medium(context);
62
- Navigator.of(context).pushNamed(
63
- bartInnerPath('home', 'features'),
64
- );
62
+ // Top-level go_router route: opens full-screen on the
63
+ // root navigator (above the BottomMenu, no bottom bar)
64
+ // and pops back to home with the menu intact.
65
+ context.push('/features');
65
66
  },
66
67
  );
67
68
  final componentsCard = _DashboardGradientCard(
@@ -74,9 +75,7 @@ class HomePage extends ConsumerWidget {
74
75
  gradient: _componentsGradient(context, isDark),
75
76
  onOpen: () {
76
77
  KasyHaptics.medium(context);
77
- Navigator.of(context).pushNamed(
78
- bartInnerPath('home', 'components'),
79
- );
78
+ context.push('/components');
80
79
  },
81
80
  );
82
81
  if (isWide) {