kasy-cli 1.38.0 → 1.39.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 (105) hide show
  1. package/lib/scaffold/CHANGELOG.json +23 -0
  2. package/lib/scaffold/backends/api/patch/README.md +15 -0
  3. package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +11 -6
  4. package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +9 -1
  5. package/lib/scaffold/backends/api/pubspec.yaml.tpl +1 -0
  6. package/lib/scaffold/backends/patch-base-hashes.json +6 -6
  7. package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +3 -1
  8. package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/index.ts +3 -0
  9. package/lib/scaffold/backends/supabase/migrations/20240101000012_welcome_decouple_from_push.sql +62 -0
  10. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +11 -6
  11. package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +7 -0
  12. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +1 -0
  13. package/lib/scaffold/shared/generator-utils.js +12 -6
  14. package/package.json +1 -1
  15. package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +23 -23
  16. package/templates/firebase/AGENTS.md +2 -2
  17. package/templates/firebase/DESIGN_SYSTEM.md +23 -8
  18. package/templates/firebase/assets/icons/apple_black.svg +3 -0
  19. package/templates/firebase/assets/icons/apple_white.svg +4 -0
  20. package/templates/firebase/assets/icons/facebook.svg +49 -0
  21. package/templates/firebase/assets/icons/google.svg +1 -0
  22. package/templates/firebase/functions/src/admin/functions.ts +2 -0
  23. package/templates/firebase/functions/src/authentication/functions.ts +13 -7
  24. package/templates/firebase/functions/src/notifications/triggers.ts +6 -2
  25. package/templates/firebase/lib/components/components.dart +5 -2
  26. package/templates/firebase/lib/components/kasy_app_bar.dart +325 -15
  27. package/templates/firebase/lib/components/kasy_card.dart +4 -0
  28. package/templates/firebase/lib/components/kasy_drop_down.dart +584 -0
  29. package/templates/firebase/lib/components/kasy_sidebar.dart +18 -6
  30. package/templates/firebase/lib/components/kasy_tabs.dart +31 -10
  31. package/templates/firebase/lib/components/kasy_text_field.dart +29 -7
  32. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +27 -18
  33. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +34 -16
  34. package/templates/firebase/lib/core/chrome/app_bar_config.dart +214 -0
  35. package/templates/firebase/lib/core/chrome/app_bar_scope.dart +102 -0
  36. package/templates/firebase/lib/core/data/api/user_api.dart +11 -0
  37. package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +55 -15
  38. package/templates/firebase/lib/core/rating/widgets/review_popup.dart +95 -30
  39. package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +11 -0
  40. package/templates/firebase/lib/core/states/logout_action.dart +11 -1
  41. package/templates/firebase/lib/core/states/user_state_notifier.dart +28 -1
  42. package/templates/firebase/lib/core/theme/texts.dart +21 -6
  43. package/templates/firebase/lib/core/theme/type_scale.dart +34 -15
  44. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +51 -19
  45. package/templates/firebase/lib/core/web_viewport_scale.dart +66 -36
  46. package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +14 -3
  47. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_composer.dart +1 -1
  48. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_conversation_view.dart +52 -35
  49. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +1 -1
  50. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +18 -8
  51. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +11 -61
  52. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +11 -61
  53. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +7 -5
  54. package/templates/firebase/lib/features/authentication/ui/widgets/social_auth_tile.dart +83 -0
  55. package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +4 -4
  56. package/templates/firebase/lib/features/home/home_components_page.dart +253 -125
  57. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +263 -59
  58. package/templates/firebase/lib/features/home/home_feed.dart +2 -2
  59. package/templates/firebase/lib/features/home/home_image_grid.dart +3 -3
  60. package/templates/firebase/lib/features/local_reminders/providers/reminder_notifier.dart +8 -1
  61. package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +111 -57
  62. package/templates/firebase/lib/features/notifications/api/device_api.dart +11 -3
  63. package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +16 -4
  64. package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +7 -56
  65. package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +5 -5
  66. package/templates/firebase/lib/features/notifications/ui/widgets/push_permission_banner.dart +163 -0
  67. package/templates/firebase/lib/features/notifications/ui/widgets/web_notifications_bell.dart +2 -2
  68. package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +9 -0
  69. package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +28 -0
  70. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +51 -12
  71. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +90 -32
  72. package/templates/firebase/lib/features/settings/settings_page.dart +53 -32
  73. package/templates/firebase/lib/features/settings/ui/components/admin/admin_home_widgets.dart +4 -0
  74. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +895 -111
  75. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +7 -0
  76. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +445 -233
  77. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +171 -41
  78. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +1 -1
  79. package/templates/firebase/lib/features/settings/ui/components/delete_user_component.dart +9 -1
  80. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +48 -47
  81. package/templates/firebase/lib/features/settings/ui/widgets/settings_bottom_sheet_option_tile.dart +21 -18
  82. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +25 -10
  83. package/templates/firebase/lib/i18n/en.i18n.json +753 -712
  84. package/templates/firebase/lib/i18n/es.i18n.json +753 -712
  85. package/templates/firebase/lib/i18n/pt.i18n.json +753 -712
  86. package/templates/firebase/lib/main.dart +20 -7
  87. package/templates/firebase/lib/router.dart +32 -26
  88. package/templates/firebase/pubspec.yaml +2 -1
  89. package/templates/firebase/test/admin_shell_chrome_test.dart +11 -5
  90. package/templates/firebase/test/app_bar_config_test.dart +70 -0
  91. package/templates/firebase/test/components/kasy_text_field_height_test.dart +77 -0
  92. package/templates/firebase/test/core/web_viewport_scale_test.dart +23 -16
  93. package/templates/firebase/tool/design_check.dart +9 -0
  94. package/templates/firebase/assets/icons/apple.png +0 -0
  95. package/templates/firebase/assets/icons/facebook.png +0 -0
  96. package/templates/firebase/assets/icons/google.png +0 -0
  97. package/templates/firebase/assets/icons/google_play_games.png +0 -0
  98. package/templates/firebase/lib/components/kasy_web_header.dart +0 -218
  99. package/templates/firebase/lib/core/chrome/web_header_scope.dart +0 -20
  100. package/templates/firebase/lib/features/authentication/ui/components/apple_signin.dart +0 -19
  101. package/templates/firebase/lib/features/authentication/ui/components/google_signin.dart +0 -32
  102. package/templates/firebase/lib/features/authentication/ui/widgets/round_signin.dart +0 -73
  103. package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -66
  104. package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +0 -179
  105. package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart +0 -106
@@ -0,0 +1,83 @@
1
+ import 'package:flutter/material.dart';
2
+ import 'package:flutter_svg/flutter_svg.dart';
3
+ import 'package:kasy_kit/core/haptics/kasy_haptics.dart';
4
+ import 'package:kasy_kit/core/theme/theme.dart';
5
+ import 'package:kasy_kit/core/widgets/kasy_hover.dart';
6
+
7
+ /// Outlined social sign-in button (Google / Apple / Facebook), shared by the
8
+ /// sign-in and sign-up screens.
9
+ ///
10
+ /// Each tile fills its slot in the social row (the parent wraps it in an
11
+ /// [Expanded]) and centers the provider glyph. It uses [KasyHover] so it has a
12
+ /// real hover highlight on web/desktop and a press fill on every platform — no
13
+ /// Material ripple, matching the rest of the kit's interactive controls. While a
14
+ /// request is in flight ([onPressed] null) it dims and stops responding to
15
+ /// pointer and keyboard.
16
+ class SocialAuthButton extends StatelessWidget {
17
+ const SocialAuthButton({
18
+ super.key,
19
+ required this.label,
20
+ required this.iconAsset,
21
+ required this.onPressed,
22
+ });
23
+
24
+ /// Accessibility label (the provider name) announced to screen readers.
25
+ final String label;
26
+
27
+ /// Path to the provider's official brand SVG bundled under `assets/icons/`
28
+ /// (e.g. `assets/icons/google.svg`). Rendered at full brand colour and at a
29
+ /// shared [KasyIconSize.lg] box so every provider stays the same size. For
30
+ /// Apple, the caller picks the black/white variant for the current theme.
31
+ final String iconAsset;
32
+
33
+ /// Tap handler. When null the button is disabled (dimmed, inert).
34
+ final VoidCallback? onPressed;
35
+
36
+ @override
37
+ Widget build(BuildContext context) {
38
+ final bool enabled = onPressed != null;
39
+ final BorderRadius radius = BorderRadius.circular(KasyRadius.sm);
40
+
41
+ // No background fill: the KasyHover overlay sits behind the transparent
42
+ // interior to provide the hover/press tint, and on the surface auth card a
43
+ // fill would be invisible anyway. The hairline border gives the outlined
44
+ // shape, consistent with KasyButton's outlined variant.
45
+ final Widget surface = Container(
46
+ height: 44,
47
+ decoration: BoxDecoration(
48
+ borderRadius: radius,
49
+ border: Border.all(
50
+ color: context.colors.outline.withValues(alpha: 0.38),
51
+ ),
52
+ ),
53
+ child: Center(
54
+ // Brand logos have different aspect ratios (Apple is tall, Google is
55
+ // square); SvgPicture defaults to BoxFit.contain, which keeps each one
56
+ // inside the shared box without distortion so the row stays balanced.
57
+ child: SvgPicture.asset(
58
+ iconAsset,
59
+ width: KasyIconSize.lg,
60
+ height: KasyIconSize.lg,
61
+ ),
62
+ ),
63
+ );
64
+
65
+ if (!enabled) {
66
+ return Opacity(opacity: 0.5, child: surface);
67
+ }
68
+
69
+ return KasyHover(
70
+ onTap: () {
71
+ KasyHaptics.medium(context);
72
+ onPressed!();
73
+ },
74
+ // KasyHover fires a light selection haptic by default; a social sign-in is
75
+ // a significant action, so opt out and fire the heavier "medium" above.
76
+ hapticEnabled: false,
77
+ focusable: true,
78
+ borderRadius: radius,
79
+ semanticLabel: label,
80
+ child: surface,
81
+ );
82
+ }
83
+ }
@@ -59,15 +59,15 @@ class _FeatureCardState extends State<FeatureCard> {
59
59
  children: [
60
60
  Text(
61
61
  widget.title,
62
- style: context.textTheme.titleMedium?.copyWith(
63
- fontWeight: FontWeight.w700,
62
+ style: context.kasyTextTheme.listRowTitle.copyWith(
63
+ color: context.colors.onSurface,
64
+ fontWeight: FontWeight.w600,
64
65
  ),
65
66
  ),
66
67
  const SizedBox(height: 2),
67
68
  Text(
68
69
  widget.description,
69
- style: context.textTheme.bodySmall?.copyWith(
70
- fontWeight: FontWeight.w400,
70
+ style: context.textTheme.bodyMedium?.copyWith(
71
71
  color: context.colors.muted,
72
72
  ),
73
73
  ),
@@ -6,6 +6,7 @@ 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
8
  import 'package:kasy_kit/features/home/home_components_preview_registry.dart';
9
+ import 'package:kasy_kit/i18n/translations.g.dart';
9
10
 
10
11
  /// UI-kit catalog, reachable from the home dashboard.
11
12
  ///
@@ -38,140 +39,267 @@ class HomeComponentsPage extends StatelessWidget {
38
39
  /// The UI-kit catalog list on its own (no app bar / scaffold), so it can be the
39
40
  /// body of [HomeComponentsPage] AND a section inside the admin console (which
40
41
  /// supplies its own chrome). Fills the space it is given.
41
- class HomeComponentsCatalog extends StatelessWidget {
42
+ class HomeComponentsCatalog extends StatefulWidget {
42
43
  const HomeComponentsCatalog({super.key});
43
44
 
45
+ @override
46
+ State<HomeComponentsCatalog> createState() => _HomeComponentsCatalogState();
47
+ }
48
+
49
+ class _HomeComponentsCatalogState extends State<HomeComponentsCatalog> {
50
+ final TextEditingController _searchCtrl = TextEditingController();
51
+ String _query = '';
52
+
53
+ @override
54
+ void dispose() {
55
+ _searchCtrl.dispose();
56
+ super.dispose();
57
+ }
58
+
59
+ /// Reset the filter when leaving for a component, so coming back shows the
60
+ /// full catalog instead of the stale search term.
61
+ void _clearSearch() {
62
+ if (!mounted) return;
63
+ if (_query.isEmpty && _searchCtrl.text.isEmpty) return;
64
+ _searchCtrl.clear();
65
+ setState(() => _query = '');
66
+ }
67
+
44
68
  @override
45
69
  Widget build(BuildContext context) {
70
+ final String q = _query.trim().toLowerCase();
71
+ final List<_CatalogRow> rows = q.isEmpty
72
+ ? _kCatalog
73
+ : _kCatalog
74
+ .where((r) => r.name.toLowerCase().contains(q))
75
+ .toList(growable: false);
76
+
46
77
  return Padding(
47
- padding: EdgeInsets.fromLTRB(
48
- KasySpacing.pageHorizontalGutter,
49
- KasySpacing.belowChromeContentGap,
50
- KasySpacing.pageHorizontalGutter,
51
- MediaQuery.paddingOf(context).bottom +
52
- KasySpacing.belowChromeContentGap,
53
- ),
54
- child: _componentsCatalogInset(
55
- context,
56
- child: Padding(
57
- padding: const EdgeInsets.symmetric(
58
- horizontal: KasySpacing.xs,
59
- ),
60
- child: ScrollConfiguration(
61
- behavior: const KasyKitScrollBehavior(),
62
- child: ListView.separated(
63
- padding: EdgeInsets.zero,
64
- itemCount: _kCatalog.length + 1,
65
- separatorBuilder: (context, index) => Divider(
66
- height: 1,
67
- thickness: 1,
68
- indent: KasySpacing.md,
69
- endIndent: KasySpacing.md,
70
- color: context.colors.outline.withValues(alpha: 0.33),
78
+ padding: EdgeInsets.fromLTRB(
79
+ KasySpacing.pageHorizontalGutter,
80
+ KasySpacing.belowChromeContentGap,
81
+ KasySpacing.pageHorizontalGutter,
82
+ MediaQuery.paddingOf(context).bottom + KasySpacing.belowChromeContentGap,
83
+ ),
84
+ child: Column(
85
+ crossAxisAlignment: CrossAxisAlignment.stretch,
86
+ children: [
87
+ _ComponentsSearchField(
88
+ controller: _searchCtrl,
89
+ onChanged: (v) => setState(() => _query = v),
90
+ ),
91
+ const SizedBox(height: KasySpacing.smd),
92
+ Expanded(
93
+ child: _componentsCatalogInset(
94
+ context,
95
+ child: rows.isEmpty
96
+ ? const _ComponentsEmptyResults()
97
+ : Padding(
98
+ padding: const EdgeInsets.symmetric(
99
+ horizontal: KasySpacing.xs,
71
100
  ),
72
- itemBuilder: (context, index) {
73
- if (index >= _kCatalog.length) {
74
- return const SizedBox(height: KasySpacing.sm);
75
- }
76
- final _CatalogRow row = _kCatalog[index];
77
- return KasyHover(
78
- hapticEnabled: false,
79
- onTap: () {
80
- KasyHaptics.selection(context);
81
- if (row.name == 'Design System') {
82
- context.push('/design-system');
83
- return;
84
- }
85
- // Skip rows without a preview yet (registry returns
86
- // null); otherwise open /components/:name, which the
87
- // router resolves back to this component's preview.
88
- if (getComponentPreviewDefinition(row.name) == null) {
89
- return;
90
- }
91
- context.push(
92
- '/components/${Uri.encodeComponent(row.name)}',
93
- );
94
- },
95
- child: Padding(
96
- padding: const EdgeInsets.symmetric(
97
- horizontal: KasySpacing.smd,
98
- vertical: 16,
99
- ),
100
- child: Row(
101
- children: [
102
- Expanded(
103
- child: Row(
104
- children: [
105
- Flexible(
106
- child: Text(
107
- row.name,
108
- // Lighter weight (w500) to match the
109
- // app's row/list scale instead of the
110
- // heavier heading weight.
111
- style: context.kasyTextTheme.rowTitle
112
- .copyWith(
113
- color:
114
- context.colors.onSurface,
115
- ),
116
- ),
117
- ),
118
- if (_kReadyComponents.contains(
119
- row.name,
120
- )) ...[
121
- const SizedBox(width: KasySpacing.sm),
122
- Text(
123
- 'Pronto',
124
- style: context.textTheme.bodySmall
125
- ?.copyWith(
126
- color: context.colors.success,
127
- fontWeight: FontWeight.w600,
128
- ),
129
- ),
130
- ],
131
- if (_kWebReadyComponents.contains(
132
- row.name,
133
- )) ...[
134
- const SizedBox(width: KasySpacing.xs),
135
- Text(
136
- 'Web',
137
- style: context.textTheme.bodySmall
138
- ?.copyWith(
139
- color: context.colors.primary,
140
- fontWeight: FontWeight.w600,
141
- ),
142
- ),
143
- ],
144
- if (_kUrgentComponents.contains(
145
- row.name,
146
- )) ...[
147
- const SizedBox(width: KasySpacing.sm),
148
- Text(
149
- 'Prioridade: Urgente',
150
- style: context.textTheme.bodySmall
151
- ?.copyWith(
152
- color: context.colors.warning,
153
- fontWeight: FontWeight.w700,
154
- ),
155
- ),
156
- ],
157
- ],
158
- ),
159
- ),
160
- Icon(
161
- KasyIcons.chevronRight,
162
- color: context.colors.muted,
163
- size: 22,
164
- ),
165
- ],
101
+ child: ScrollConfiguration(
102
+ behavior: const KasyKitScrollBehavior(),
103
+ child: ListView.separated(
104
+ padding: EdgeInsets.zero,
105
+ itemCount: rows.length + 1,
106
+ separatorBuilder: (context, index) => Divider(
107
+ height: 1,
108
+ thickness: 1,
109
+ indent: KasySpacing.md,
110
+ endIndent: KasySpacing.md,
111
+ color: context.colors.outline.withValues(
112
+ alpha: 0.33,
166
113
  ),
167
114
  ),
168
- );
115
+ itemBuilder: (context, index) {
116
+ if (index >= rows.length) {
117
+ return const SizedBox(height: KasySpacing.sm);
118
+ }
119
+ return _catalogRow(context, rows[index]);
120
+ },
121
+ ),
122
+ ),
123
+ ),
124
+ ),
125
+ ),
126
+ ],
127
+ ),
128
+ );
129
+ }
169
130
 
170
- },
131
+ Widget _catalogRow(BuildContext context, _CatalogRow row) {
132
+ return KasyHover(
133
+ hapticEnabled: false,
134
+ onTap: () {
135
+ KasyHaptics.selection(context);
136
+ if (row.name == 'Design System') {
137
+ context.push('/design-system').whenComplete(_clearSearch);
138
+ return;
139
+ }
140
+ // Skip rows without a preview yet (registry returns null); otherwise
141
+ // open /components/:name, which the router resolves back to this
142
+ // component's preview.
143
+ if (getComponentPreviewDefinition(row.name) == null) {
144
+ return;
145
+ }
146
+ context
147
+ .push('/components/${Uri.encodeComponent(row.name)}')
148
+ .whenComplete(_clearSearch);
149
+ },
150
+ child: Padding(
151
+ padding: const EdgeInsets.symmetric(
152
+ horizontal: KasySpacing.smd,
153
+ vertical: 16,
154
+ ),
155
+ child: Row(
156
+ children: [
157
+ Expanded(
158
+ child: Row(
159
+ children: [
160
+ Flexible(
161
+ child: Text(
162
+ row.name,
163
+ // Lighter weight (w500) to match the app's row/list scale
164
+ // instead of the heavier heading weight.
165
+ style: context.kasyTextTheme.rowTitle.copyWith(
166
+ color: context.colors.onSurface,
167
+ ),
171
168
  ),
172
169
  ),
173
- ),
170
+ if (_kReadyComponents.contains(row.name)) ...[
171
+ const SizedBox(width: KasySpacing.sm),
172
+ Text(
173
+ 'Pronto',
174
+ style: context.textTheme.bodySmall?.copyWith(
175
+ color: context.colors.success,
176
+ fontWeight: FontWeight.w600,
177
+ ),
178
+ ),
179
+ ],
180
+ if (_kWebReadyComponents.contains(row.name)) ...[
181
+ const SizedBox(width: KasySpacing.xs),
182
+ Text(
183
+ 'Web',
184
+ style: context.textTheme.bodySmall?.copyWith(
185
+ color: context.colors.primary,
186
+ fontWeight: FontWeight.w600,
187
+ ),
188
+ ),
189
+ ],
190
+ if (_kUrgentComponents.contains(row.name)) ...[
191
+ const SizedBox(width: KasySpacing.sm),
192
+ Text(
193
+ 'Prioridade: Urgente',
194
+ style: context.textTheme.bodySmall?.copyWith(
195
+ color: context.colors.warning,
196
+ fontWeight: FontWeight.w700,
197
+ ),
198
+ ),
199
+ ],
200
+ ],
201
+ ),
202
+ ),
203
+ Icon(
204
+ KasyIcons.chevronRight,
205
+ color: context.colors.muted,
206
+ size: 22,
207
+ ),
208
+ ],
209
+ ),
210
+ ),
211
+ );
212
+ }
213
+ }
214
+
215
+ /// Compact search field above the catalog — filters the list by component name.
216
+ class _ComponentsSearchField extends StatelessWidget {
217
+ final TextEditingController controller;
218
+ final ValueChanged<String> onChanged;
219
+ const _ComponentsSearchField({
220
+ required this.controller,
221
+ required this.onChanged,
222
+ });
223
+
224
+ @override
225
+ Widget build(BuildContext context) {
226
+ return SizedBox(
227
+ height: 40,
228
+ child: TextField(
229
+ controller: controller,
230
+ onChanged: onChanged,
231
+ style: context.textTheme.bodyMedium,
232
+ decoration: InputDecoration(
233
+ hintText: t.home.dashboard.search_hint,
234
+ hintStyle: context.textTheme.bodyMedium?.copyWith(
235
+ color: context.colors.muted,
236
+ ),
237
+ prefixIcon: Icon(
238
+ Icons.search_rounded,
239
+ size: 18,
240
+ color: context.colors.muted,
241
+ ),
242
+ prefixIconConstraints: const BoxConstraints(
243
+ minWidth: 40,
244
+ minHeight: 40,
245
+ ),
246
+ isDense: true,
247
+ contentPadding: const EdgeInsets.symmetric(
248
+ horizontal: 12,
249
+ vertical: 10,
250
+ ),
251
+ filled: true,
252
+ fillColor: context.colors.surface,
253
+ enabledBorder: OutlineInputBorder(
254
+ borderRadius: BorderRadius.circular(KasyRadius.sm),
255
+ borderSide: BorderSide(
256
+ color: context.colors.outline.withValues(alpha: 0.5),
257
+ ),
258
+ ),
259
+ focusedBorder: OutlineInputBorder(
260
+ borderRadius: BorderRadius.circular(KasyRadius.sm),
261
+ borderSide: BorderSide(color: context.colors.primary, width: 1.4),
262
+ ),
263
+ border: OutlineInputBorder(
264
+ borderRadius: BorderRadius.circular(KasyRadius.sm),
265
+ borderSide: BorderSide(
266
+ color: context.colors.outline.withValues(alpha: 0.5),
267
+ ),
268
+ ),
269
+ ),
270
+ ),
271
+ );
272
+ }
273
+ }
274
+
275
+ /// Shown inside the catalog surface when a search matches nothing.
276
+ class _ComponentsEmptyResults extends StatelessWidget {
277
+ const _ComponentsEmptyResults();
278
+
279
+ @override
280
+ Widget build(BuildContext context) {
281
+ return Center(
282
+ child: Padding(
283
+ padding: const EdgeInsets.all(KasySpacing.lg),
284
+ child: Column(
285
+ mainAxisSize: MainAxisSize.min,
286
+ children: [
287
+ Icon(
288
+ Icons.search_off_rounded,
289
+ size: 30,
290
+ color: context.colors.muted,
291
+ ),
292
+ const SizedBox(height: KasySpacing.sm),
293
+ Text(
294
+ t.home.dashboard.search_empty,
295
+ textAlign: TextAlign.center,
296
+ style: context.textTheme.bodySmall?.copyWith(
297
+ color: context.colors.muted,
174
298
  ),
299
+ ),
300
+ ],
301
+ ),
302
+ ),
175
303
  );
176
304
  }
177
305
  }
@@ -195,6 +323,7 @@ const Set<String> _kReadyComponents = <String>{
195
323
  'DatePicker',
196
324
  'Design System',
197
325
  'Dialog',
326
+ 'DropDown',
198
327
  'Hover',
199
328
  'Tabs',
200
329
  'TextArea',
@@ -205,7 +334,6 @@ const Set<String> _kReadyComponents = <String>{
205
334
  'SwipeAction',
206
335
  'TextFieldOTP',
207
336
  'Toast',
208
- 'Web Header',
209
337
  };
210
338
 
211
339
  /// Components that have been adapted for web (cursor, hover, shadow, border).
@@ -222,6 +350,7 @@ const Set<String> _kWebReadyComponents = <String>{
222
350
  'DatePicker',
223
351
  'Design System',
224
352
  'Dialog',
353
+ 'DropDown',
225
354
  'Hover',
226
355
  'Sidebar',
227
356
  'Skeleton',
@@ -231,7 +360,6 @@ const Set<String> _kWebReadyComponents = <String>{
231
360
  'TextField',
232
361
  'TextFieldOTP',
233
362
  'Toast',
234
- 'Web Header',
235
363
  };
236
364
 
237
365
  const Set<String> _kUrgentComponents = <String>{
@@ -254,6 +382,7 @@ const List<_CatalogRow> _kCatalog = [
254
382
  _CatalogRow('Checkbox'),
255
383
  _CatalogRow('DatePicker'),
256
384
  _CatalogRow('Dialog'),
385
+ _CatalogRow('DropDown'),
257
386
  _CatalogRow('Hover'),
258
387
  _CatalogRow('Sidebar'),
259
388
  _CatalogRow('Skeleton'),
@@ -263,7 +392,6 @@ const List<_CatalogRow> _kCatalog = [
263
392
  _CatalogRow('TextField'),
264
393
  _CatalogRow('TextFieldOTP'),
265
394
  _CatalogRow('Toast'),
266
- _CatalogRow('Web Header'),
267
395
  // Prioridade: Urgente (_kUrgentComponents), A–Z
268
396
  _CatalogRow('Radio Group'),
269
397
  _CatalogRow('Switch'),