kasy-cli 1.32.0 → 1.34.0

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 (60) hide show
  1. package/bin/kasy.js +42 -0
  2. package/lib/commands/apple-web.js +222 -0
  3. package/lib/commands/configure.js +3 -91
  4. package/lib/commands/doctor.js +20 -0
  5. package/lib/commands/facebook.js +189 -0
  6. package/lib/commands/new.js +50 -2
  7. package/lib/scaffold/CHANGELOG.json +18 -0
  8. package/lib/scaffold/backends/firebase/setup-from-scratch.js +164 -0
  9. package/lib/scaffold/backends/supabase/deploy.js +92 -0
  10. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +10 -0
  11. package/lib/scaffold/shared/generator-utils.js +18 -6
  12. package/lib/utils/apple-web.js +147 -0
  13. package/lib/utils/facebook.js +162 -0
  14. package/lib/utils/i18n/messages-en.js +62 -0
  15. package/lib/utils/i18n/messages-es.js +62 -0
  16. package/lib/utils/i18n/messages-pt.js +62 -0
  17. package/package.json +2 -2
  18. package/templates/firebase/AGENTS.md +87 -0
  19. package/templates/firebase/CLAUDE.md +16 -0
  20. package/templates/firebase/DESIGN_SYSTEM.md +234 -0
  21. package/templates/firebase/docs/auth-setup.en.md +2 -2
  22. package/templates/firebase/docs/auth-setup.es.md +2 -2
  23. package/templates/firebase/docs/auth-setup.pt.md +2 -2
  24. package/templates/firebase/lib/components/components.dart +1 -0
  25. package/templates/firebase/lib/components/kasy_app_bar.dart +4 -1
  26. package/templates/firebase/lib/components/kasy_screen.dart +114 -0
  27. package/templates/firebase/lib/components/kasy_toast.dart +39 -70
  28. package/templates/firebase/lib/core/chrome/chrome_visibility.dart +22 -0
  29. package/templates/firebase/lib/core/config/features.dart +5 -0
  30. package/templates/firebase/lib/core/rating/widgets/review_popup.dart +46 -124
  31. package/templates/firebase/lib/core/widgets/update_bottom_sheet.dart +29 -126
  32. package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +11 -7
  33. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_chat_composer.dart +21 -0
  34. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +1 -1
  35. package/templates/firebase/lib/features/authentication/api/authentication_api.dart +61 -0
  36. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +21 -15
  37. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +20 -14
  38. package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +1 -2
  39. package/templates/firebase/lib/features/home/home_components_page.dart +7 -1
  40. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +32 -0
  41. package/templates/firebase/lib/features/local_reminders/ui/reminder_page.dart +165 -209
  42. package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +2 -2
  43. package/templates/firebase/lib/features/notifications/ui/components/notification_tile.dart +21 -8
  44. package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart +2 -2
  45. package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +3 -6
  46. package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +77 -126
  47. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +3 -4
  48. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +2 -4
  49. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +2 -1
  50. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +17 -8
  51. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +2 -2
  52. package/templates/firebase/lib/i18n/en.i18n.json +5 -4
  53. package/templates/firebase/lib/i18n/es.i18n.json +5 -4
  54. package/templates/firebase/lib/i18n/pt.i18n.json +5 -4
  55. package/templates/firebase/lib/router.dart +2 -0
  56. package/templates/firebase/pubspec.yaml +1 -1
  57. package/templates/firebase/tool/design_check.dart +152 -0
  58. package/templates/firebase/assets/images/review.png +0 -0
  59. package/templates/firebase/assets/images/update.png +0 -0
  60. package/templates/firebase/lib/features/notifications/ui/components/notifications_header.dart +0 -32
@@ -91,7 +91,13 @@ class HomeComponentsPage extends StatelessWidget {
91
91
  Flexible(
92
92
  child: Text(
93
93
  row.name,
94
- style: context.textTheme.titleMedium,
94
+ // Lighter weight (w500) to match the
95
+ // app's row/list scale instead of the
96
+ // heavier heading weight.
97
+ style: context.textTheme.titleMedium
98
+ ?.copyWith(
99
+ fontWeight: FontWeight.w500,
100
+ ),
95
101
  ),
96
102
  ),
97
103
  if (_kReadyComponents.contains(
@@ -397,6 +397,10 @@ ComponentPreviewDefinition? getComponentPreviewDefinition(
397
397
  label: 'With Label & Description',
398
398
  builder: _buildTextAreaWithLabelDescription,
399
399
  ),
400
+ ComponentPreviewVariant(
401
+ label: 'Variants',
402
+ builder: _buildTextAreaVariants,
403
+ ),
400
404
  ComponentPreviewVariant(
401
405
  label: 'States',
402
406
  builder: _buildTextAreaStates,
@@ -2785,6 +2789,34 @@ Widget _buildTextAreaWithLabelDescription(BuildContext context) {
2785
2789
  return const _TextAreaWithLabelDescriptionContent();
2786
2790
  }
2787
2791
 
2792
+ Widget _buildTextAreaVariants(BuildContext context) {
2793
+ return const Column(
2794
+ mainAxisSize: MainAxisSize.min,
2795
+ crossAxisAlignment: CrossAxisAlignment.stretch,
2796
+ children: [
2797
+ KasyTextArea(
2798
+ label: 'Primary Variant',
2799
+ hint: 'Primary style text area',
2800
+ description: 'Default variant with primary styling',
2801
+ ),
2802
+ SizedBox(height: KasySpacing.lg),
2803
+ KasyTextArea(
2804
+ label: 'Secondary Variant',
2805
+ hint: 'Secondary style text area',
2806
+ description: 'Secondary variant for surfaces',
2807
+ variant: KasyTextFieldVariant.secondary,
2808
+ ),
2809
+ SizedBox(height: KasySpacing.lg),
2810
+ KasyTextArea(
2811
+ label: 'Flat Variant',
2812
+ hint: 'Flat style text area',
2813
+ description: 'Primary fill, hairline border, no shadow',
2814
+ variant: KasyTextFieldVariant.flat,
2815
+ ),
2816
+ ],
2817
+ );
2818
+ }
2819
+
2788
2820
  Widget _buildTextAreaStates(BuildContext context) {
2789
2821
  return const Column(
2790
2822
  mainAxisSize: MainAxisSize.min,
@@ -1,11 +1,15 @@
1
1
  import 'package:flutter/material.dart';
2
2
  import 'package:flutter_riverpod/flutter_riverpod.dart';
3
3
  import 'package:kasy_kit/components/kasy_app_bar.dart';
4
+ import 'package:kasy_kit/components/kasy_card.dart';
5
+ import 'package:kasy_kit/components/kasy_chip.dart';
6
+ import 'package:kasy_kit/components/kasy_date_picker.dart';
7
+ import 'package:kasy_kit/components/kasy_tabs.dart';
4
8
  import 'package:kasy_kit/core/theme/theme.dart';
5
- import 'package:kasy_kit/core/widgets/kasy_focus_ring.dart';
6
9
  import 'package:kasy_kit/core/widgets/kasy_scroll_behavior.dart';
7
10
  import 'package:kasy_kit/features/local_reminders/providers/reminder_notifier.dart';
8
11
  import 'package:kasy_kit/features/local_reminders/repositories/reminder_preferences.dart';
12
+ import 'package:kasy_kit/features/settings/ui/widgets/settings_tile.dart';
9
13
  import 'package:kasy_kit/i18n/translations.g.dart';
10
14
 
11
15
  class ReminderPage extends ConsumerWidget {
@@ -44,163 +48,133 @@ class _ReminderForm extends ConsumerWidget {
44
48
 
45
49
  const _ReminderForm({required this.state});
46
50
 
51
+ // Tab order mirrors the type selector left-to-right.
52
+ static const List<ReminderType> _types = <ReminderType>[
53
+ ReminderType.daily,
54
+ ReminderType.weekly,
55
+ ReminderType.specificDate,
56
+ ];
57
+
47
58
  @override
48
59
  Widget build(BuildContext context, WidgetRef ref) {
49
60
  final tr = Translations.of(context).reminderPage;
50
61
  final notifier = ref.read(reminderProvider.notifier);
51
62
 
52
- return Column(
53
- crossAxisAlignment: CrossAxisAlignment.stretch,
54
- children: [
55
- SwitchListTile(
56
- title: Text(tr.toggleLabel, style: context.textTheme.bodyLarge),
57
- value: state.enabled,
58
- onChanged: notifier.setEnabled,
59
- ),
60
- if (state.enabled) ...[
61
- const Divider(),
62
- const SizedBox(height: KasySpacing.sm),
63
- Text(
64
- tr.typeLabel,
65
- style: context.textTheme.labelLarge?.copyWith(
66
- color: context.colors.muted,
67
- ),
68
- ),
69
- const SizedBox(height: KasySpacing.sm),
70
- _TypeSelector(current: state.type, onChanged: notifier.setType),
71
- const SizedBox(height: KasySpacing.lg),
72
- Text(
73
- tr.timeLabel,
74
- style: context.textTheme.labelLarge?.copyWith(
75
- color: context.colors.muted,
76
- ),
77
- ),
78
- const SizedBox(height: KasySpacing.sm),
79
- _TimeTile(
80
- hour: state.hour,
81
- minute: state.minute,
82
- onChanged: (h, m) => notifier.setTime(h, m),
63
+ return Center(
64
+ child: ConstrainedBox(
65
+ constraints: const BoxConstraints(maxWidth: 600),
66
+ child: Padding(
67
+ padding: const EdgeInsets.fromLTRB(
68
+ KasySpacing.pageHorizontalGutter,
69
+ KasySpacing.belowChromeContentGap,
70
+ KasySpacing.pageHorizontalGutter,
71
+ KasySpacing.xl,
83
72
  ),
84
- if (state.type == ReminderType.weekly) ...[
85
- const SizedBox(height: KasySpacing.lg),
86
- Text(
87
- tr.dayLabel,
88
- style: context.textTheme.labelLarge?.copyWith(
89
- color: context.colors.muted,
90
- ),
91
- ),
92
- const SizedBox(height: KasySpacing.sm),
93
- _DaySelector(
94
- current: state.dayOfWeek,
95
- onChanged: notifier.setDayOfWeek,
96
- ),
97
- ],
98
- if (state.type == ReminderType.specificDate) ...[
99
- const SizedBox(height: KasySpacing.lg),
100
- Text(
101
- tr.dateLabel,
102
- style: context.textTheme.labelLarge?.copyWith(
103
- color: context.colors.muted,
73
+ child: Column(
74
+ crossAxisAlignment: CrossAxisAlignment.stretch,
75
+ children: [
76
+ KasyCard(
77
+ padding: const EdgeInsets.symmetric(
78
+ horizontal: KasySpacing.md,
79
+ vertical: KasySpacing.xs,
80
+ ),
81
+ child: SettingsSwitchTile(
82
+ icon: KasyIcons.notification,
83
+ title: tr.toggleLabel,
84
+ value: state.enabled,
85
+ onChanged: notifier.setEnabled,
86
+ ),
104
87
  ),
105
- ),
106
- const SizedBox(height: KasySpacing.sm),
107
- _DateTile(date: state.date, onChanged: notifier.setDate),
108
- ],
109
- ],
110
- ],
111
- );
112
- }
113
- }
114
-
115
- class _TypeSelector extends StatelessWidget {
116
- final ReminderType current;
117
- final ValueChanged<ReminderType> onChanged;
118
-
119
- const _TypeSelector({required this.current, required this.onChanged});
120
-
121
- @override
122
- Widget build(BuildContext context) {
123
- final tr = Translations.of(context).reminderPage;
124
- return SegmentedButton<ReminderType>(
125
- segments: [
126
- ButtonSegment(value: ReminderType.daily, label: Text(tr.daily)),
127
- ButtonSegment(value: ReminderType.weekly, label: Text(tr.weekly)),
128
- ButtonSegment(
129
- value: ReminderType.specificDate,
130
- label: Text(tr.specificDate),
88
+ if (state.enabled) ...[
89
+ const SizedBox(height: KasySpacing.xl),
90
+ _FieldLabel(tr.typeLabel),
91
+ const SizedBox(height: KasySpacing.sm),
92
+ // Default hug mode scrolls horizontally when the labels don't
93
+ // fit (long localized strings) instead of overflowing the row.
94
+ KasyTabs(
95
+ tabs: [tr.daily, tr.weekly, tr.specificDate],
96
+ selectedIndex: _types.indexOf(state.type),
97
+ onTabSelected: (i) => notifier.setType(_types[i]),
98
+ ),
99
+ // daily / weekly schedule by a wall-clock time (hour + minute);
100
+ // specificDate carries its own time inside the chosen date.
101
+ if (state.type == ReminderType.daily ||
102
+ state.type == ReminderType.weekly) ...[
103
+ const SizedBox(height: KasySpacing.lg),
104
+ _FieldLabel(tr.timeLabel),
105
+ const SizedBox(height: KasySpacing.sm),
106
+ _TimeTile(
107
+ hour: state.hour,
108
+ minute: state.minute,
109
+ onChanged: (h, m) => notifier.setTime(h, m),
110
+ ),
111
+ ],
112
+ if (state.type == ReminderType.weekly) ...[
113
+ const SizedBox(height: KasySpacing.lg),
114
+ _FieldLabel(tr.dayLabel),
115
+ const SizedBox(height: KasySpacing.sm),
116
+ _DaySelector(
117
+ current: state.dayOfWeek,
118
+ onChanged: notifier.setDayOfWeek,
119
+ ),
120
+ ],
121
+ if (state.type == ReminderType.specificDate) ...[
122
+ const SizedBox(height: KasySpacing.lg),
123
+ _FieldLabel(tr.dateLabel),
124
+ const SizedBox(height: KasySpacing.sm),
125
+ KasyDatePicker(
126
+ value: state.date,
127
+ minDate: DateTime.now(),
128
+ onChanged: (picked) {
129
+ // Keep the previously chosen time when the day changes.
130
+ final DateTime? base = state.date;
131
+ notifier.setDate(
132
+ DateTime(
133
+ picked.year,
134
+ picked.month,
135
+ picked.day,
136
+ base?.hour ?? 9,
137
+ base?.minute ?? 0,
138
+ ),
139
+ );
140
+ },
141
+ ),
142
+ const SizedBox(height: KasySpacing.lg),
143
+ _FieldLabel(tr.timeLabel),
144
+ const SizedBox(height: KasySpacing.sm),
145
+ _TimeTile(
146
+ hour: state.date?.hour ?? 9,
147
+ minute: state.date?.minute ?? 0,
148
+ onChanged: (h, m) {
149
+ // Keep the previously chosen day when the time changes.
150
+ final DateTime base = state.date ?? DateTime.now();
151
+ notifier.setDate(
152
+ DateTime(base.year, base.month, base.day, h, m),
153
+ );
154
+ },
155
+ ),
156
+ ],
157
+ ],
158
+ ],
159
+ ),
131
160
  ),
132
- ],
133
- selected: {current},
134
- onSelectionChanged: (s) => onChanged(s.first),
135
- style: ButtonStyle(
136
- textStyle: WidgetStateProperty.all(context.textTheme.labelSmall),
137
161
  ),
138
162
  );
139
163
  }
140
164
  }
141
165
 
142
- class _TimeTile extends StatelessWidget {
143
- final int hour;
144
- final int minute;
145
- final void Function(int hour, int minute) onChanged;
166
+ /// Quiet group eyebrow above each field — the design-system section label
167
+ /// (small, gently tracked, muted), matching the Settings / sidebar pattern.
168
+ class _FieldLabel extends StatelessWidget {
169
+ final String label;
146
170
 
147
- const _TimeTile({
148
- required this.hour,
149
- required this.minute,
150
- required this.onChanged,
151
- });
152
-
153
- String _pad(int v) => v.toString().padLeft(2, '0');
171
+ const _FieldLabel(this.label);
154
172
 
155
173
  @override
156
174
  Widget build(BuildContext context) {
157
- Future<void> pickTime() async {
158
- final picked = await showTimePicker(
159
- context: context,
160
- initialTime: TimeOfDay(hour: hour, minute: minute),
161
- );
162
- if (picked != null) {
163
- onChanged(picked.hour, picked.minute);
164
- }
165
- }
166
-
167
- return KasyFocusRing(
168
- onActivate: pickTime,
169
- borderRadius: BorderRadius.circular(KasySpacing.sm),
170
- child: InkWell(
171
- canRequestFocus: false,
172
- onTap: pickTime,
173
- borderRadius: BorderRadius.circular(KasySpacing.sm),
174
- child: Container(
175
- padding: const EdgeInsets.symmetric(
176
- horizontal: KasySpacing.md,
177
- vertical: KasySpacing.smd,
178
- ),
179
- decoration: BoxDecoration(
180
- color: context.colors.surface,
181
- borderRadius: BorderRadius.circular(KasySpacing.sm),
182
- ),
183
- child: Row(
184
- children: [
185
- Icon(KasyIcons.time, color: context.colors.primary),
186
- const SizedBox(width: KasySpacing.sm),
187
- Text(
188
- '${_pad(hour)}:${_pad(minute)}',
189
- style: context.textTheme.headlineMedium?.copyWith(
190
- color: context.colors.onSurface,
191
- fontWeight: FontWeight.bold,
192
- ),
193
- ),
194
- const Spacer(),
195
- Icon(
196
- KasyIcons.arrowForwardIos,
197
- size: KasyIconSize.xs,
198
- color: context.colors.muted,
199
- ),
200
- ],
201
- ),
202
- ),
203
- ),
175
+ return Text(
176
+ label,
177
+ style: KasyTextTheme.sectionLabel.copyWith(color: context.colors.muted),
204
178
  );
205
179
  }
206
180
  }
@@ -217,12 +191,12 @@ class _DaySelector extends StatelessWidget {
217
191
  final days = ['Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb', 'Dom'];
218
192
  return Wrap(
219
193
  spacing: KasySpacing.xs,
194
+ runSpacing: KasySpacing.xs,
220
195
  children: List.generate(7, (i) {
221
196
  final dayIndex = i + 1;
222
- final selected = current == dayIndex;
223
- return ChoiceChip(
224
- label: Text(days[i]),
225
- selected: selected,
197
+ return KasyChip(
198
+ label: days[i],
199
+ selected: current == dayIndex,
226
200
  onSelected: (_) => onChanged(dayIndex),
227
201
  );
228
202
  }),
@@ -230,82 +204,64 @@ class _DaySelector extends StatelessWidget {
230
204
  }
231
205
  }
232
206
 
233
- class _DateTile extends StatelessWidget {
234
- final DateTime? date;
235
- final ValueChanged<DateTime> onChanged;
207
+ /// A tappable field that opens the time picker. Built on [KasyCard] so it
208
+ /// inherits the design-system surface, press depth and keyboard focus ring.
209
+ class _TimeTile extends StatelessWidget {
210
+ final int hour;
211
+ final int minute;
212
+ final void Function(int hour, int minute) onChanged;
236
213
 
237
- const _DateTile({required this.date, required this.onChanged});
214
+ const _TimeTile({
215
+ required this.hour,
216
+ required this.minute,
217
+ required this.onChanged,
218
+ });
219
+
220
+ String _pad(int v) => v.toString().padLeft(2, '0');
238
221
 
239
222
  @override
240
223
  Widget build(BuildContext context) {
241
- final label = date != null
242
- ? '${date!.day.toString().padLeft(2, '0')}/${date!.month.toString().padLeft(2, '0')}/${date!.year} ${date!.hour.toString().padLeft(2, '0')}:${date!.minute.toString().padLeft(2, '0')}'
243
- : Translations.of(context).reminderPage.selectDate;
244
-
245
- Future<void> pickDateTime() async {
246
- final now = DateTime.now();
247
- final pickedDate = await showDatePicker(
248
- context: context,
249
- initialDate: date ?? now,
250
- firstDate: now,
251
- lastDate: now.add(const Duration(days: 365)),
252
- );
253
- if (pickedDate == null || !context.mounted) return;
254
- final pickedTime = await showTimePicker(
224
+ Future<void> pickTime() async {
225
+ final picked = await showTimePicker(
255
226
  context: context,
256
- initialTime: TimeOfDay(
257
- hour: date?.hour ?? 9,
258
- minute: date?.minute ?? 0,
259
- ),
260
- );
261
- if (pickedTime == null) return;
262
- onChanged(
263
- DateTime(
264
- pickedDate.year,
265
- pickedDate.month,
266
- pickedDate.day,
267
- pickedTime.hour,
268
- pickedTime.minute,
269
- ),
227
+ initialTime: TimeOfDay(hour: hour, minute: minute),
270
228
  );
229
+ if (picked != null) {
230
+ onChanged(picked.hour, picked.minute);
231
+ }
271
232
  }
272
233
 
273
- return KasyFocusRing(
274
- onActivate: pickDateTime,
275
- borderRadius: BorderRadius.circular(KasySpacing.sm),
276
- child: InkWell(
277
- canRequestFocus: false,
278
- onTap: pickDateTime,
279
- borderRadius: BorderRadius.circular(KasySpacing.sm),
280
- child: Container(
281
- padding: const EdgeInsets.symmetric(
282
- horizontal: KasySpacing.md,
283
- vertical: KasySpacing.smd,
234
+ return KasyCard(
235
+ onTap: pickTime,
236
+ borderRadius: KasyRadius.mdBorderRadius,
237
+ padding: const EdgeInsets.symmetric(
238
+ horizontal: KasySpacing.md,
239
+ vertical: KasySpacing.smd,
240
+ ),
241
+ child: Row(
242
+ children: [
243
+ Icon(
244
+ KasyIcons.time,
245
+ size: KasyIconSize.lg,
246
+ color: context.colors.primary,
284
247
  ),
285
- decoration: BoxDecoration(
286
- color: context.colors.surface,
287
- borderRadius: BorderRadius.circular(KasySpacing.sm),
248
+ const SizedBox(width: KasySpacing.sm),
249
+ Text(
250
+ '${_pad(hour)}:${_pad(minute)}',
251
+ style: context.textTheme.bodyLarge?.copyWith(
252
+ color: context.colors.onSurface,
253
+ fontWeight: FontWeight.w600,
254
+ ),
288
255
  ),
289
- child: Row(
290
- children: [
291
- Icon(KasyIcons.calendar, color: context.colors.primary),
292
- const SizedBox(width: KasySpacing.sm),
293
- Text(
294
- label,
295
- style: context.textTheme.bodyLarge?.copyWith(
296
- color: context.colors.onSurface,
297
- ),
298
- ),
299
- const Spacer(),
300
- Icon(
301
- KasyIcons.arrowForwardIos,
302
- size: KasyIconSize.xs,
303
- color: context.colors.muted,
304
- ),
305
- ],
256
+ const Spacer(),
257
+ Icon(
258
+ KasyIcons.arrowForwardIos,
259
+ size: KasyIconSize.xs,
260
+ color: context.colors.muted,
306
261
  ),
307
- ),
262
+ ],
308
263
  ),
309
264
  );
310
265
  }
311
266
  }
267
+
@@ -121,8 +121,8 @@ class _NotificationSettingsSheetState
121
121
  title: tr.push_title,
122
122
  value: _isEnabled,
123
123
  iconBackgroundColor: _isEnabled
124
- ? const Color(0xFFFF6B35)
125
- : const Color(0xFF78909C),
124
+ ? const Color(0xFFFF6B35) // design-check: ignore — category accent
125
+ : const Color(0xFF78909C), // design-check: ignore — category accent
126
126
  onChanged: _toggling ? (_) {} : _onToggle,
127
127
  ),
128
128
  // subtitle below
@@ -1,5 +1,6 @@
1
1
  import 'package:flutter/material.dart';
2
2
  import 'package:flutter_animate/flutter_animate.dart';
3
+ import 'package:kasy_kit/components/kasy_card.dart';
3
4
  import 'package:kasy_kit/core/theme/theme.dart';
4
5
  import 'package:kasy_kit/core/widgets/kasy_hover.dart';
5
6
  import 'package:kasy_kit/features/notifications/providers/models/notification.dart'
@@ -31,15 +32,27 @@ class NotificationTileComponent extends StatelessWidget {
31
32
  ],
32
33
  delay: index < 6 ? Duration(milliseconds: 60 * index) : Duration.zero,
33
34
  onComplete: (controller) => controller.stop(),
34
- child: KasyHover(
35
- onTap: () => onTap?.call(notification),
36
- borderRadius: KasyRadius.smBorderRadius,
37
- focusable: true,
35
+ // Card surface (elevated, radius 16, hairline border + soft shadow) to
36
+ // match the Settings card; the hover/press feedback and keyboard focus
37
+ // ring live inside on the surface, exactly like a Settings row.
38
+ child: KasyCard(
38
39
  margin: const EdgeInsets.only(top: KasySpacing.sm),
39
- child: NotificationTile.from(
40
- key: ValueKey('notification_${notification.id}'),
41
- context,
42
- notification,
40
+ borderRadius: KasyRadius.lgBorderRadius,
41
+ padding: EdgeInsets.zero,
42
+ child: KasyHover(
43
+ onTap: () => onTap?.call(notification),
44
+ borderRadius: KasyRadius.lgBorderRadius,
45
+ focusable: true,
46
+ focusGapColor: context.colors.surface,
47
+ padding: const EdgeInsets.symmetric(
48
+ horizontal: KasySpacing.md,
49
+ vertical: KasySpacing.smd,
50
+ ),
51
+ child: NotificationTile.from(
52
+ key: ValueKey('notification_${notification.id}'),
53
+ context,
54
+ notification,
55
+ ),
43
56
  ),
44
57
  ),
45
58
  );
@@ -72,8 +72,8 @@ class _PushNotificationSwitcherState
72
72
  title: tr.push_title,
73
73
  value: _isEnabled,
74
74
  iconBackgroundColor: _isEnabled
75
- ? const Color(0xFFFF6B35)
76
- : const Color(0xFF78909C),
75
+ ? const Color(0xFFFF6B35) // design-check: ignore — category accent
76
+ : const Color(0xFF78909C), // design-check: ignore — category accent
77
77
  onChanged: _loading ? (_) {} : _onChanged,
78
78
  ),
79
79
  Padding(
@@ -257,12 +257,9 @@ class _GroupLabel extends StatelessWidget {
257
257
  ),
258
258
  child: Text(
259
259
  label.toUpperCase(),
260
- style: context.textTheme.labelSmall?.copyWith(
261
- fontSize: 11,
262
- color: context.colors.muted,
263
- letterSpacing: 1.4,
264
- fontWeight: FontWeight.w700,
265
- ),
260
+ // Design-system section-eyebrow role (same as Settings' section labels),
261
+ // instead of a bespoke 11/w700 style.
262
+ style: KasyTextTheme.sectionLabel.copyWith(color: context.colors.muted),
266
263
  ),
267
264
  );
268
265
  }